├── .gitignore ├── README.md ├── core-build-report.html ├── core.js ├── core.js.wasm ├── deps.edn ├── index.html └── src ├── browser ├── Browser.java └── Callback.java ├── build.clj └── core.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | classes 3 | *.wat 4 | graal 5 | *.iml 6 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 1. Install sdkman `curl -s "https://get.sdkman.io" | bash` 2 | 2. Install GraalVM 25 `sdk install java 25.ea.18-graal` 3 | 3. Enable GraalVM 25 `sdk use java 25.ea.18-graal` 4 | 4. Install binaryen `brew install binaryen` 5 | 5. Replace `$JAVA_HOME` in `deps.edn` with a path to your local GraalVM installation (`echo $JAVA_HOME`) 6 | 6. Compile Java classes `javac -parameters -cp $(clj -Spath):$JAVA_HOME/lib/svm/tools/svm-wasm/builder/svm-wasm-api.jar -d target/classes src/browser/Callback.java src/browser/Browser.java` 7 | 7. Build wasm executable `clojure -M:native-image` 8 | 8. Start web server `python3 -m http.server 8000` go to `http://localhost:8000/` in your browser, press a button on the page, should see an alert 9 | -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * @suppress {checkVars,checkTypes,duplicate} 6 | * @nocollapse 7 | */ 8 | var GraalVM = {}; 9 | (function() {(function() {(function() { 10 | 11 | /* 12 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 13 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 14 | * 15 | * This code is free software; you can redistribute it and/or modify it 16 | * under the terms of the GNU General Public License version 2 only, as 17 | * published by the Free Software Foundation. Oracle designates this 18 | * particular file as subject to the "Classpath" exception as provided 19 | * by Oracle in the LICENSE file that accompanied this code. 20 | * 21 | * This code is distributed in the hope that it will be useful, but WITHOUT 22 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 23 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 24 | * version 2 for more details (a copy is included in the LICENSE file that 25 | * accompanied this code). 26 | * 27 | * You should have received a copy of the GNU General Public License version 28 | * 2 along with this work; if not, write to the Free Software Foundation, 29 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 30 | * 31 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 32 | * or visit www.oracle.com if you need additional information or have any 33 | * questions. 34 | */ 35 | 36 | /** 37 | * Represents a potential feature of the JS runtime. 38 | * 39 | * During construction, the detection callback is called to detect whether the runtime supports the feature. 40 | * The callback returns whether the feature was detected and may store some data 41 | * into the 'data' member for future use. 42 | */ 43 | class Feature { 44 | constructor(descr, detection_callback) { 45 | this.description = descr; 46 | this.data = {}; 47 | this.detected = detection_callback(this.data); 48 | } 49 | } 50 | 51 | /** 52 | * Specialized feature to detect global variables. 53 | */ 54 | class GlobalVariableFeature extends Feature { 55 | constructor(descr, var_name) { 56 | super(descr, GlobalVariableFeature.detection_callback.bind(null, var_name)); 57 | } 58 | 59 | /** 60 | * Function to detect a global variable. 61 | * 62 | * Uses the 'globalThis' object which should represent the global scope in 63 | * modern runtimes. 64 | */ 65 | static detection_callback(var_name, data) { 66 | if (var_name in globalThis) { 67 | data.global = globalThis[var_name]; 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * Returns the global detected by this feature. 76 | */ 77 | get() { 78 | return this.data.global; 79 | } 80 | } 81 | 82 | /** 83 | * Specialized feature to detect the presence of Node.js modules. 84 | */ 85 | class RequireFeature extends Feature { 86 | constructor(descr, module_name) { 87 | super(descr, RequireFeature.detection_callback.bind(null, module_name)); 88 | } 89 | 90 | /** 91 | * Function to detect a Node.js module. 92 | */ 93 | static detection_callback(module_name, data) { 94 | if (typeof require != "function") { 95 | return false; 96 | } 97 | 98 | try { 99 | data.module = require(module_name); 100 | return true; 101 | } catch (e) { 102 | return false; 103 | } 104 | } 105 | 106 | /** 107 | * Returns the module detected by this feature. 108 | */ 109 | get() { 110 | return this.data.module; 111 | } 112 | } 113 | 114 | /** 115 | * Collection of features needed for runtime functions. 116 | */ 117 | let features = { 118 | // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API 119 | fetch: new GlobalVariableFeature("Presence of the Fetch API", "fetch"), 120 | 121 | // https://nodejs.org/api/fs.html#promises-api 122 | node_fs: new RequireFeature("Presence of Node.js fs promises module", "fs/promises"), 123 | 124 | // https://nodejs.org/api/https.html 125 | node_https: new RequireFeature("Presence of Node.js https module", "https"), 126 | 127 | // https://nodejs.org/api/process.html 128 | node_process: new RequireFeature("Presence of Node.js process module", "process"), 129 | 130 | /** 131 | * Technically, '__filename' is not a global variable, it is a variable in the module scope. 132 | * https://nodejs.org/api/globals.html#__filename 133 | */ 134 | filename: new Feature("Presence of __filename global", (d) => { 135 | if (typeof __filename != "undefined") { 136 | d.filename = __filename; 137 | return true; 138 | } 139 | 140 | return false; 141 | }), 142 | 143 | // https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript 144 | currentScript: new Feature("Presence of document.currentScript global", (d) => { 145 | if ( 146 | typeof document != "undefined" && 147 | "currentScript" in document && 148 | document.currentScript != null && 149 | "src" in document.currentScript 150 | ) { 151 | d.currentScript = document.currentScript; 152 | return true; 153 | } 154 | 155 | return false; 156 | }), 157 | 158 | // https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/location 159 | location: new Feature("Presence of Web worker location", (d) => { 160 | if (typeof self != "undefined") { 161 | d.location = self.location; 162 | return true; 163 | } 164 | return false; 165 | }), 166 | }; 167 | 168 | 169 | /* 170 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 171 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 172 | * 173 | * This code is free software; you can redistribute it and/or modify it 174 | * under the terms of the GNU General Public License version 2 only, as 175 | * published by the Free Software Foundation. Oracle designates this 176 | * particular file as subject to the "Classpath" exception as provided 177 | * by Oracle in the LICENSE file that accompanied this code. 178 | * 179 | * This code is distributed in the hope that it will be useful, but WITHOUT 180 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 181 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 182 | * version 2 for more details (a copy is included in the LICENSE file that 183 | * accompanied this code). 184 | * 185 | * You should have received a copy of the GNU General Public License version 186 | * 2 along with this work; if not, write to the Free Software Foundation, 187 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 188 | * 189 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 190 | * or visit www.oracle.com if you need additional information or have any 191 | * questions. 192 | */ 193 | function charArrayToString(arr) { 194 | let res = []; 195 | 196 | const len = 512; 197 | 198 | for (let i = 0; i < arr.length; i += len) { 199 | res.push(String.fromCharCode(...arr.slice(i, i + len))); 200 | } 201 | return res.join(""); 202 | } 203 | 204 | 205 | /* 206 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 207 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 208 | * 209 | * This code is free software; you can redistribute it and/or modify it 210 | * under the terms of the GNU General Public License version 2 only, as 211 | * published by the Free Software Foundation. Oracle designates this 212 | * particular file as subject to the "Classpath" exception as provided 213 | * by Oracle in the LICENSE file that accompanied this code. 214 | * 215 | * This code is distributed in the hope that it will be useful, but WITHOUT 216 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 217 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 218 | * version 2 for more details (a copy is included in the LICENSE file that 219 | * accompanied this code). 220 | * 221 | * You should have received a copy of the GNU General Public License version 222 | * 2 along with this work; if not, write to the Free Software Foundation, 223 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 224 | * 225 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 226 | * or visit www.oracle.com if you need additional information or have any 227 | * questions. 228 | */ 229 | function llog(p) { 230 | if (p instanceof Error) { 231 | console.log(p); 232 | } else if (p instanceof Object) { 233 | console.log(p.toString()); 234 | } else { 235 | console.log(p); 236 | } 237 | } 238 | 239 | /** 240 | * A writer emulating stdout and stderr using console.log and console.error 241 | * 242 | * Since those functions cannot print without newline, lines are buffered but 243 | * without a max buffer size. 244 | */ 245 | class ConsoleWriter { 246 | constructor(logger) { 247 | this.line = ""; 248 | this.newline = "\n".charCodeAt(0); 249 | this.closed = false; 250 | this.logger = logger; 251 | } 252 | 253 | printChars(chars) { 254 | let index = chars.lastIndexOf(this.newline); 255 | 256 | if (index >= 0) { 257 | this.line += charArrayToString(chars.slice(0, index)); 258 | this.writeLine(); 259 | chars = chars.slice(index + 1); 260 | } 261 | 262 | this.line += charArrayToString(chars); 263 | } 264 | 265 | writeLine() { 266 | this.logger(this.line); 267 | this.line = ""; 268 | } 269 | 270 | flush() { 271 | if (this.line.length > 0) { 272 | // In JS we cannot print without newline, so flushing will always produce one 273 | this.writeLine(); 274 | } 275 | } 276 | 277 | close() { 278 | if (this.closed) { 279 | return; 280 | } 281 | this.closed = true; 282 | 283 | this.flush(); 284 | } 285 | } 286 | 287 | var stdoutWriter = new ConsoleWriter(console.log); 288 | var stderrWriter = new ConsoleWriter(console.error); 289 | 290 | 291 | /* 292 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 293 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 294 | * 295 | * This code is free software; you can redistribute it and/or modify it 296 | * under the terms of the GNU General Public License version 2 only, as 297 | * published by the Free Software Foundation. Oracle designates this 298 | * particular file as subject to the "Classpath" exception as provided 299 | * by Oracle in the LICENSE file that accompanied this code. 300 | * 301 | * This code is distributed in the hope that it will be useful, but WITHOUT 302 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 303 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 304 | * version 2 for more details (a copy is included in the LICENSE file that 305 | * accompanied this code). 306 | * 307 | * You should have received a copy of the GNU General Public License version 308 | * 2 along with this work; if not, write to the Free Software Foundation, 309 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 310 | * 311 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 312 | * or visit www.oracle.com if you need additional information or have any 313 | * questions. 314 | */ 315 | 316 | /** 317 | * Class that holds the configuration of the VM. 318 | */ 319 | class Config { 320 | constructor() { 321 | this.libraries = {}; 322 | this.currentWorkingDirectory = "/root"; 323 | } 324 | } 325 | 326 | /** 327 | * Class that holds the data required to start the VM. 328 | */ 329 | class Data { 330 | constructor(config) { 331 | /** 332 | * User-specified configuration object. 333 | * 334 | * @type Config 335 | */ 336 | this.config = config; 337 | 338 | /** 339 | * Optionally holds a binary support file. 340 | */ 341 | this.binaryImageHeap = null; 342 | 343 | /** 344 | * Maps the library names to prefetched content during VM bootup. 345 | * After the VM is initialized, the keys are retained, but values are set to null. 346 | */ 347 | this.libraries = {}; 348 | } 349 | } 350 | 351 | /** 352 | * Dummy object that exists only to avoid warnings in IDEs for the `$t[name]` expressions (used in JavaScript support files). 353 | */ 354 | const $t = {}; 355 | 356 | /** 357 | * For a given JavaScript class, returns an object that can lookup its properties. 358 | */ 359 | function cprops(cls) { 360 | return cls.prototype; 361 | } 362 | 363 | /** 364 | * Placeholder for lazy-value initializers. 365 | */ 366 | class LazyValueThunk { 367 | constructor(initializer) { 368 | this.initializer = initializer; 369 | } 370 | } 371 | 372 | /** 373 | * Creates a lazy property on the specified object. 374 | */ 375 | function lazy(obj, name, initializer) { 376 | let state = new LazyValueThunk(initializer); 377 | Object.defineProperty(obj, name, { 378 | configurable: false, 379 | enumerable: true, 380 | get: () => { 381 | if (state instanceof LazyValueThunk) { 382 | state = state.initializer(); 383 | } 384 | return state; 385 | }, 386 | set: () => { 387 | throw new Error("Property is not writable."); 388 | }, 389 | }); 390 | } 391 | 392 | /** 393 | * Placeholder for a method and its signature. 394 | */ 395 | class MethodMetadata { 396 | constructor(method, isStatic, returnHub, ...paramHubs) { 397 | this.method = method; 398 | this.isStatic = isStatic; 399 | this.returnHub = returnHub; 400 | this.paramHubs = paramHubs; 401 | } 402 | } 403 | 404 | /** 405 | * Create MethodMetadata object for an instance method. 406 | */ 407 | function mmeta(method, returnHub, ...paramHubs) { 408 | return new MethodMetadata(method, false, returnHub, ...paramHubs); 409 | } 410 | 411 | /** 412 | * Create MethodMetadata object for a static method. 413 | */ 414 | function smmeta(method, returnHub, ...paramHubs) { 415 | return new MethodMetadata(method, true, returnHub, ...paramHubs); 416 | } 417 | 418 | /** 419 | * Describes extra class metadata used by the runtime. 420 | */ 421 | class ClassMetadata { 422 | /** 423 | * Constructs the class metadata. 424 | * 425 | * @param ft Field table 426 | * @param singleAbstractMethod Method metadata for the single abstract method, when the class implements exactly one functional interface 427 | * @param methodTable Dictionary mapping each method name to the list of overloaded signatures 428 | */ 429 | constructor(ft, singleAbstractMethod = undefined, methodTable = undefined) { 430 | this.ft = ft; 431 | this.singleAbstractMethod = singleAbstractMethod; 432 | this.methodTable = methodTable; 433 | } 434 | } 435 | 436 | /** 437 | * Class for the various runtime utilities. 438 | */ 439 | class Runtime { 440 | constructor() { 441 | this.isLittleEndian = false; 442 | /** 443 | * Dictionary of all initializer functions. 444 | */ 445 | this.jsResourceInits = {}; 446 | /** 447 | * The data object of the current VM, which contains the configuration settings, 448 | * optionally a binary-encoded image heap, and other resources. 449 | * 450 | * The initial data value is present in the enclosing scope. 451 | * 452 | * @type Data 453 | */ 454 | this.data = null; 455 | /** 456 | * Map from full Java class names to corresponding Java hubs, for classes that are accessible outside of the image. 457 | */ 458 | this.hubs = {}; 459 | /** 460 | * The table of native functions that can be invoked via indirect calls. 461 | * 462 | * The index in this table represents the address of the function. 463 | * The zero-th entry is always set to null. 464 | */ 465 | this.funtab = [null]; 466 | /** 467 | * Map of internal symbols that are used during execution. 468 | */ 469 | Object.defineProperty(this, "symbol", { 470 | writable: false, 471 | configurable: false, 472 | value: { 473 | /** 474 | * Symbol used to symbolically get the to-JavaScript-native coercion object on the Java proxy. 475 | * 476 | * This symbol is available as a property on Java proxies, and will return a special object 477 | * that can coerce the Java proxy to various native JavaScript values. 478 | * 479 | * See the ProxyHandler class for more details. 480 | */ 481 | javaScriptCoerceAs: Symbol("__javascript_coerce_as__"), 482 | 483 | /** 484 | * Key used by Web Image to store the corresponding JS native value as a property of JSValue objects. 485 | * 486 | * Used by the JS annotation. 487 | * 488 | * Use conversion.setJavaScriptNative and conversion.extractJavaScriptNative to access that property 489 | * instead of using this symbol directly. 490 | */ 491 | javaScriptNative: Symbol("__javascript_native__"), 492 | 493 | /** 494 | * Key used to store a property value (inside a JavaScript object) that contains the Java-native object. 495 | * 496 | * Used by the JS annotation. 497 | */ 498 | javaNative: Symbol("__java_native__"), 499 | 500 | /** 501 | * Key used to store the runtime-generated proxy handler inside the Java class. 502 | * 503 | * The handler is created lazily the first time that the corresponding class is added 504 | * 505 | * Use getOrCreateProxyHandler to retrieve the proxy handler instead of using this symbol directly. 506 | */ 507 | javaProxyHandler: Symbol("__java_proxy_handler__"), 508 | 509 | /** 510 | * Key used to store the extra class metadata when emitting Java classes. 511 | */ 512 | classMeta: Symbol("__class_metadata__"), 513 | 514 | /** 515 | * Key for the hub-object property that points to the corresponding generated JavaScript class. 516 | */ 517 | jsClass: Symbol("__js_class__"), 518 | 519 | /** 520 | * Key for the property on primitive hubs, which points to the corresponding boxed hub. 521 | */ 522 | boxedHub: Symbol("__boxed_hub__"), 523 | 524 | /** 525 | * Key for the property on primitive hubs, which holds the boxing function. 526 | */ 527 | box: Symbol("__box__"), 528 | 529 | /** 530 | * Key for the property on primitive hubs, which holds the unboxing function. 531 | */ 532 | unbox: Symbol("__unbox__"), 533 | 534 | /** 535 | * Key for the constructor-overload list that is stored in the class metadata. 536 | */ 537 | ctor: Symbol("__ctor__"), 538 | 539 | /** 540 | * Internal value passed to JavaScript mirror-class constructors 541 | * to denote that the mirrored class was instantiated from Java. 542 | * 543 | * This is used when a JSObject subclass gets constructed from Java. 544 | */ 545 | skipJavaCtor: Symbol("__skip_java_ctor__"), 546 | 547 | /** 548 | * Symbol for the Java toString method. 549 | */ 550 | toString: Symbol("__toString__"), 551 | }, 552 | }); 553 | 554 | // Conversion-related functions and values. 555 | // The following values are set or used by the jsconversion module. 556 | 557 | /** 558 | * The holder of JavaScript mirror class for JSObject subclasses. 559 | */ 560 | this.mirrors = {}; 561 | /** 562 | * Reference to the hub of the java.lang.Class class. 563 | */ 564 | this.classHub = null; 565 | /** 566 | * Function that retrieves the hub of the specified Java object. 567 | */ 568 | this.hubOf = null; 569 | /** 570 | * Function that checks if the first argument hub is the supertype or the same as the second argument hub. 571 | */ 572 | this.isSupertype = null; 573 | /** 574 | * Mapping from JavaScript classes that were imported to the list of internal Java classes 575 | * under which the corresponding JavaScript class was imported. 576 | * See JS.Import annotation. 577 | */ 578 | this.importMap = new Map(); 579 | } 580 | 581 | /** 582 | * Use the build-time endianness at run-time. 583 | * 584 | * Unsafe operations that write values to byte arrays at build-time assumes the 585 | * endianness of the build machine. Therefore, unsafe read and write operations 586 | * at run-time need to assume the same endianness. 587 | */ 588 | setEndianness(isLittleEndian) { 589 | runtime.isLittleEndian = isLittleEndian; 590 | } 591 | 592 | /** 593 | * Ensures that there is a Set entry for the given JavaScript class, and returns it. 594 | */ 595 | ensureFacadeSetFor(cls) { 596 | let facades = this.importMap.get(cls); 597 | if (facades === undefined) { 598 | facades = new Set(); 599 | this.importMap.set(cls, facades); 600 | } 601 | return facades; 602 | } 603 | 604 | /** 605 | * Finds the set of Java facade classes for the given JavaScript class, or an empty set if there are none. 606 | */ 607 | findFacadesFor(cls) { 608 | let facades = this.importMap.get(cls); 609 | if (facades === undefined) { 610 | facades = new Set(); 611 | } 612 | return facades; 613 | } 614 | 615 | _ensurePackage(container, name) { 616 | const elements = name === "" ? [] : name.split("."); 617 | let current = container; 618 | for (let i = 0; i < elements.length; i++) { 619 | const element = elements[i]; 620 | current = element in current ? current[element] : (current[element] = {}); 621 | } 622 | return current; 623 | } 624 | 625 | /** 626 | * Get or create the specified exported JavaScript mirror-class export package on the VM object. 627 | * 628 | * @param name Full Java name of the package 629 | */ 630 | ensureExportPackage(name) { 631 | return this._ensurePackage(vm.exports, name); 632 | } 633 | 634 | /** 635 | * Get or create the specified exported JavaScript mirror-class package on the Runtime object. 636 | * 637 | * @param name Full Java name of the package 638 | */ 639 | ensureVmPackage(name) { 640 | return this._ensurePackage(runtime.mirrors, name); 641 | } 642 | 643 | /** 644 | * Get an existing exported JavaScript mirror class on the Runtime object. 645 | * 646 | * @param className Full Java name of the class 647 | */ 648 | vmClass(className) { 649 | const elements = className.split("."); 650 | let current = runtime.mirrors; 651 | for (let i = 0; i < elements.length; i++) { 652 | const element = elements[i]; 653 | if (element in current) { 654 | current = current[element]; 655 | } else { 656 | return null; 657 | } 658 | } 659 | return current; 660 | } 661 | 662 | /** 663 | * Returns the array with all the prefetched library names. 664 | */ 665 | prefetchedLibraryNames() { 666 | const names = []; 667 | for (const name in this.data.libraries) { 668 | names.push(name); 669 | } 670 | return names; 671 | } 672 | 673 | /** 674 | * Adds a function to the function table. 675 | * 676 | * @param f The function to add 677 | * @returns {number} The address of the newly added function 678 | */ 679 | addToFuntab(f) { 680 | this.funtab.push(f); 681 | return runtime.funtab.length - 1; 682 | } 683 | 684 | /** 685 | * Fetches binary data from the given url. 686 | * 687 | * @param url 688 | * @returns {!Promise} 689 | */ 690 | fetchData(url) { 691 | return Promise.reject(new Error("fetchData is not supported")); 692 | } 693 | 694 | /** 695 | * Fetches UTF8 text from the given url. 696 | * 697 | * @param url 698 | * @returns {!Promise} 699 | */ 700 | fetchText(url) { 701 | return Promise.reject(new Error("fetchText is not supported")); 702 | } 703 | 704 | /** 705 | * Sets the exit code for the VM. 706 | */ 707 | setExitCode(c) { 708 | vm.exitCode = c; 709 | } 710 | 711 | /** 712 | * Returns the absolute path of the JS file WebImage is running in. 713 | * 714 | * Depending on the runtime, this may be a URL or an absolute filesystem path. 715 | * @returns {!String} 716 | */ 717 | getCurrentFile() { 718 | throw new Error("getCurrentFile is not supported"); 719 | } 720 | } 721 | 722 | /** 723 | * Instance of the internal runtime state of the VM. 724 | */ 725 | const runtime = new Runtime(); 726 | 727 | /** 728 | * VM state that is exposed, and which represents the VM API accessible to external users. 729 | */ 730 | class VM { 731 | constructor() { 732 | this.exitCode = 0; 733 | this.exports = {}; 734 | this.symbol = {}; 735 | /** 736 | * The to-JavaScript-native coercion symbol in the external API. 737 | */ 738 | Object.defineProperty(this.symbol, "as", { 739 | configurable: false, 740 | enumerable: true, 741 | writable: false, 742 | value: runtime.symbol.javaScriptCoerceAs, 743 | }); 744 | /** 745 | * The symbol for the Java toString method in the external API. 746 | */ 747 | Object.defineProperty(this.symbol, "toString", { 748 | configurable: false, 749 | enumerable: true, 750 | writable: false, 751 | value: runtime.symbol.toString, 752 | }); 753 | } 754 | 755 | /** 756 | * Coerce the specified JavaScript value to the specified Java type. 757 | * 758 | * For precise summary of the coercion rules, please see the JS annotation JavaDoc. 759 | * 760 | * The implementation for this function is injected later. 761 | * 762 | * @param javaScriptValue The JavaScript value to coerce 763 | * @param type The name of the Java class to coerce to, or a Java Proxy representing the target class. 764 | * @returns {*} The closest corresponding Java Proxy value 765 | */ 766 | as(javaScriptValue, type) { 767 | throw new Error("VM.as is not supported in this backend or it was called too early"); 768 | } 769 | } 770 | 771 | /** 772 | * Instance of the class that represents the public VM API. 773 | */ 774 | const vm = new VM(); 775 | 776 | if (features.node_fs.detected && features.node_https.detected) { 777 | runtime.fetchText = (url) => { 778 | if (url.startsWith("http://") || url.startsWith("https://")) { 779 | return new Promise((fulfill, reject) => { 780 | let content = []; 781 | features.node_https 782 | .get() 783 | .get(url, (r) => { 784 | r.on("data", (data) => { 785 | content.push(data); 786 | }); 787 | r.on("end", () => { 788 | fulfill(content.join("")); 789 | }); 790 | }) 791 | .on("error", (e) => { 792 | reject(e); 793 | }); 794 | }); 795 | } else { 796 | return features.node_fs.get().readFile(url, "utf8"); 797 | } 798 | }; 799 | runtime.fetchData = (url) => { 800 | return features.node_fs 801 | .get() 802 | .readFile(url) 803 | .then((d) => d.buffer); 804 | }; 805 | } else if (features.fetch.detected) { 806 | runtime.fetchText = (url) => 807 | features.fetch 808 | .get()(url) 809 | .then((r) => r.text()); 810 | runtime.fetchData = (url) => 811 | features.fetch 812 | .get()(url) 813 | .then((response) => { 814 | if (!response.ok) { 815 | throw new Error(`Failed to load data at '${url}': ${response.status} ${response.statusText}`); 816 | } 817 | return response.arrayBuffer(); 818 | }); 819 | } 820 | 821 | if (features.node_process.detected) { 822 | // Extend the setExitCode function to also set the exit code of the runtime. 823 | let oldFun = runtime.setExitCode; 824 | runtime.setExitCode = (exitCode) => { 825 | oldFun(exitCode); 826 | features.node_process.get().exitCode = exitCode; 827 | }; 828 | } 829 | 830 | if (features.filename.detected) { 831 | runtime.getCurrentFile = () => features.filename.data.filename; 832 | } else if (features.currentScript.detected) { 833 | runtime.getCurrentFile = () => features.currentScript.data.currentScript.src; 834 | } else if (features.location.detected) { 835 | runtime.getCurrentFile = () => features.location.data.location.href; 836 | } 837 | 838 | 839 | /* 840 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 841 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 842 | * 843 | * This code is free software; you can redistribute it and/or modify it 844 | * under the terms of the GNU General Public License version 2 only, as 845 | * published by the Free Software Foundation. Oracle designates this 846 | * particular file as subject to the "Classpath" exception as provided 847 | * by Oracle in the LICENSE file that accompanied this code. 848 | * 849 | * This code is distributed in the hope that it will be useful, but WITHOUT 850 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 851 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 852 | * version 2 for more details (a copy is included in the LICENSE file that 853 | * accompanied this code). 854 | * 855 | * You should have received a copy of the GNU General Public License version 856 | * 2 along with this work; if not, write to the Free Software Foundation, 857 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 858 | * 859 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 860 | * or visit www.oracle.com if you need additional information or have any 861 | * questions. 862 | */ 863 | 864 | /** 865 | * Imports object passed to the WASM module during instantiation. 866 | * 867 | * @see WasmImports 868 | */ 869 | const wasmImports = {}; 870 | 871 | /** 872 | * Imports for operations that cannot be performed (or be easily emulated) in WASM. 873 | */ 874 | wasmImports.compat = { 875 | f64rem: (x, y) => x % y, 876 | f64log: Math.log, 877 | f64log10: Math.log10, 878 | f64sin: Math.sin, 879 | f64cos: Math.cos, 880 | f64tan: Math.tan, 881 | f64tanh: Math.tanh, 882 | f64exp: Math.exp, 883 | f64pow: Math.pow, 884 | f32rem: (x, y) => x % y, 885 | }; 886 | 887 | /** 888 | * Imports relating to I/O. 889 | */ 890 | wasmImports.io = {}; 891 | 892 | /** 893 | * Loads and instantiates the appropriate WebAssembly module. 894 | * 895 | * The module path is given by config.wasm_path, if specified, otherwise it is loaded relative to the current script file. 896 | */ 897 | async function wasmInstantiate(config, args) { 898 | const wasmPath = config.wasm_path || runtime.getCurrentFile() + ".wasm"; 899 | const file = await runtime.fetchData(wasmPath); 900 | const result = await WebAssembly.instantiate(file, wasmImports); 901 | return { 902 | instance: result.instance, 903 | memory: result.instance.exports.memory, 904 | }; 905 | } 906 | 907 | /** 908 | * Runs the main entry point of the given WebAssembly module. 909 | */ 910 | function wasmRun(args) { 911 | try { 912 | doRun(args); 913 | } catch (e) { 914 | console.log("Uncaught internal error:"); 915 | console.log(e); 916 | runtime.setExitCode(1); 917 | } 918 | } 919 | 920 | function getExports() { 921 | return runtime.data.wasm.instance.exports; 922 | } 923 | 924 | function getExport(name) { 925 | return getExports()[name]; 926 | } 927 | 928 | 929 | /* 930 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 931 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 932 | * 933 | * This code is free software; you can redistribute it and/or modify it 934 | * under the terms of the GNU General Public License version 2 only, as 935 | * published by the Free Software Foundation. Oracle designates this 936 | * particular file as subject to the "Classpath" exception as provided 937 | * by Oracle in the LICENSE file that accompanied this code. 938 | * 939 | * This code is distributed in the hope that it will be useful, but WITHOUT 940 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 941 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 942 | * version 2 for more details (a copy is included in the LICENSE file that 943 | * accompanied this code). 944 | * 945 | * You should have received a copy of the GNU General Public License version 946 | * 2 along with this work; if not, write to the Free Software Foundation, 947 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 948 | * 949 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 950 | * or visit www.oracle.com if you need additional information or have any 951 | * questions. 952 | */ 953 | 954 | /** 955 | * Constructs Java string from JavaScript string. 956 | */ 957 | function toJavaString(jsStr) { 958 | const length = jsStr.length; 959 | const charArray = getExport("array.char.create")(length); 960 | 961 | for (let i = 0; i < length; i++) { 962 | getExport("array.char.write")(charArray, i, jsStr.charCodeAt(i)); 963 | } 964 | 965 | return getExport("string.fromchars")(charArray); 966 | } 967 | 968 | /** 969 | * Constructs a Java string array (String[]) from a JavaScript array of 970 | * JavaScript strings. 971 | */ 972 | function toJavaStringArray(jsStrings) { 973 | const length = jsStrings.length; 974 | const stringArray = getExport("array.string.create")(length); 975 | 976 | for (let i = 0; i < length; i++) { 977 | getExport("array.object.write")(stringArray, i, toJavaString(jsStrings[i])); 978 | } 979 | 980 | return stringArray; 981 | } 982 | 983 | 984 | /* 985 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 986 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 987 | * 988 | * This code is free software; you can redistribute it and/or modify it 989 | * under the terms of the GNU General Public License version 2 only, as 990 | * published by the Free Software Foundation. Oracle designates this 991 | * particular file as subject to the "Classpath" exception as provided 992 | * by Oracle in the LICENSE file that accompanied this code. 993 | * 994 | * This code is distributed in the hope that it will be useful, but WITHOUT 995 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 996 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 997 | * version 2 for more details (a copy is included in the LICENSE file that 998 | * accompanied this code). 999 | * 1000 | * You should have received a copy of the GNU General Public License version 1001 | * 2 along with this work; if not, write to the Free Software Foundation, 1002 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1003 | * 1004 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1005 | * or visit www.oracle.com if you need additional information or have any 1006 | * questions. 1007 | */ 1008 | 1009 | /** 1010 | * Checks if the given string is an array index. 1011 | * 1012 | * Proxied accesses always get a string property (even for indexed accesses) so 1013 | * we need to check if the property access is an indexed access. 1014 | * 1015 | * A property is an index if the numeric index it is refering to has the same 1016 | * string representation as the original property. 1017 | * E.g the string '010' is a property while '10' is an index. 1018 | */ 1019 | function isArrayIndex(property) { 1020 | try { 1021 | return Number(property).toString() === property; 1022 | } catch (e) { 1023 | // Catch clause because not all property keys (e.g. symbols) can be 1024 | // converted to a number. 1025 | return false; 1026 | } 1027 | } 1028 | 1029 | /** 1030 | * Proxy handler that proxies all array element and length accesses to a Wasm 1031 | * array and everything else to the underlying array. 1032 | * 1033 | * See `proxyArray` function for more information. 1034 | */ 1035 | class ArrayProxyHandler { 1036 | /** 1037 | * Reference to the Wasm-world object. 1038 | * 1039 | * In this case, this will be a Wasm struct containing a Wasm array. 1040 | */ 1041 | #wasmObject; 1042 | 1043 | /** 1044 | * Immutable length of the array, determined during construction. 1045 | */ 1046 | #length; 1047 | 1048 | /** 1049 | * A callable (usually an exported function) that accepts the Wasm object 1050 | * and an index and returns the element at that location. 1051 | */ 1052 | #reader; 1053 | 1054 | constructor(wasmObject, reader) { 1055 | this.#wasmObject = wasmObject; 1056 | this.#length = getExport("array.length")(wasmObject); 1057 | this.#reader = reader; 1058 | } 1059 | 1060 | #isInBounds(idx) { 1061 | return idx >= 0 && idx < this.#length; 1062 | } 1063 | 1064 | #getElement(idx) { 1065 | /* 1066 | * We need an additional bounds check here because Wasm will trap, 1067 | * while JS expects an undefined value. 1068 | */ 1069 | if (this.#isInBounds(idx)) { 1070 | return this.#reader(this.#wasmObject, idx); 1071 | } else { 1072 | return undefined; 1073 | } 1074 | } 1075 | 1076 | defineProperty() { 1077 | throw new TypeError("This array is immutable. Attempted to call defineProperty"); 1078 | } 1079 | 1080 | deleteProperty() { 1081 | throw new TypeError("This array is immutable. Attempted to call deleteProperty"); 1082 | } 1083 | 1084 | /** 1085 | * Indexed accesses and the `length` property are serviced from the Wasm 1086 | * object, everything else goes to the underlying object. 1087 | */ 1088 | get(target, property, receiver) { 1089 | if (isArrayIndex(property)) { 1090 | return this.#getElement(property); 1091 | } else if (property == "length") { 1092 | return this.#length; 1093 | } else { 1094 | return Reflect.get(target, property, receiver); 1095 | } 1096 | } 1097 | 1098 | getOwnPropertyDescriptor(target, property) { 1099 | if (isArrayIndex(property)) { 1100 | return { 1101 | value: this.#getElement(property), 1102 | writable: false, 1103 | enumerable: true, 1104 | configurable: false, 1105 | }; 1106 | } else { 1107 | return Reflect.getOwnPropertyDescriptor(target, property); 1108 | } 1109 | } 1110 | 1111 | has(target, property) { 1112 | if (isArrayIndex(property)) { 1113 | return this.#isInBounds(Number(property)); 1114 | } else { 1115 | return Reflect.has(target, property); 1116 | } 1117 | } 1118 | 1119 | isExtensible() { 1120 | return false; 1121 | } 1122 | 1123 | /** 1124 | * Returns the array's own enumerable string-keyed property names. 1125 | * 1126 | * For arrays this is simply an array of all indices in string form. 1127 | */ 1128 | ownKeys() { 1129 | return Object.keys(Array.from({ length: this.#length }, (x, i) => i)); 1130 | } 1131 | 1132 | preventExtensions() { 1133 | // Do nothing this object is already not extensible 1134 | } 1135 | 1136 | set() { 1137 | throw new TypeError("This array is immutable. Attempted to call set"); 1138 | } 1139 | 1140 | setPrototypeOf() { 1141 | throw new TypeError("This array is immutable. Attempted to call setPrototypeOf"); 1142 | } 1143 | } 1144 | 1145 | /** 1146 | * Creates a read-only view on a Wasm array that looks like a JavaScript Array. 1147 | * 1148 | * The proxy is backed by an Array instance (albeit an empty one) and any 1149 | * accesses except for `length` and indexed accesses (see `isArrayIndex`) are 1150 | * proxied to the original object. 1151 | * Because the `Array.prototype` methods are all generic and only access 1152 | * `length` and the indices, this works. For example if `indexOf` is called on 1153 | * the proxy, `Array.prototype.indexOf` is called with the proxy bound to 1154 | * `this`, thus the `indexOf` implementation goes through the proxy when 1155 | * accessing elements or determining the array length. 1156 | */ 1157 | function proxyArray(a, reader) { 1158 | return new Proxy(new Array(), new ArrayProxyHandler(a, reader)); 1159 | } 1160 | 1161 | function proxyCharArray(a) { 1162 | return proxyArray(a, getExport("array.char.read")); 1163 | } 1164 | 1165 | wasmImports.convert = {}; 1166 | wasmImports.convert.proxyCharArray = proxyCharArray; 1167 | 1168 | 1169 | /* 1170 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 1171 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1172 | * 1173 | * This code is free software; you can redistribute it and/or modify it 1174 | * under the terms of the GNU General Public License version 2 only, as 1175 | * published by the Free Software Foundation. Oracle designates this 1176 | * particular file as subject to the "Classpath" exception as provided 1177 | * by Oracle in the LICENSE file that accompanied this code. 1178 | * 1179 | * This code is distributed in the hope that it will be useful, but WITHOUT 1180 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1181 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1182 | * version 2 for more details (a copy is included in the LICENSE file that 1183 | * accompanied this code). 1184 | * 1185 | * You should have received a copy of the GNU General Public License version 1186 | * 2 along with this work; if not, write to the Free Software Foundation, 1187 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1188 | * 1189 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1190 | * or visit www.oracle.com if you need additional information or have any 1191 | * questions. 1192 | */ 1193 | 1194 | function doRun(args) { 1195 | getExport("main")(toJavaStringArray(args)); 1196 | } 1197 | 1198 | 1199 | /* 1200 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 1201 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1202 | * 1203 | * This code is free software; you can redistribute it and/or modify it 1204 | * under the terms of the GNU General Public License version 2 only, as 1205 | * published by the Free Software Foundation. Oracle designates this 1206 | * particular file as subject to the "Classpath" exception as provided 1207 | * by Oracle in the LICENSE file that accompanied this code. 1208 | * 1209 | * This code is distributed in the hope that it will be useful, but WITHOUT 1210 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1211 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1212 | * version 2 for more details (a copy is included in the LICENSE file that 1213 | * accompanied this code). 1214 | * 1215 | * You should have received a copy of the GNU General Public License version 1216 | * 2 along with this work; if not, write to the Free Software Foundation, 1217 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1218 | * 1219 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1220 | * or visit www.oracle.com if you need additional information or have any 1221 | * questions. 1222 | */ 1223 | const STACK_TRACE_MARKER = "NATIVE-IMAGE-MARKER"; 1224 | 1225 | /** 1226 | * Create JavaScript Error object, which is used to fill in the Throwable.backtrace object. 1227 | */ 1228 | function genBacktrace() { 1229 | return new Error(STACK_TRACE_MARKER); 1230 | } 1231 | 1232 | /** 1233 | * Extract a Java string from the given backtrace object, which is supposed to be a JavaScript Error object. 1234 | */ 1235 | function formatStackTrace(backtrace) { 1236 | let trace; 1237 | 1238 | if (backtrace.stack) { 1239 | let lines = backtrace.stack.split("\n"); 1240 | 1241 | /* 1242 | * Since Error.prototype.stack is non-standard, different runtimes set 1243 | * it differently. 1244 | * We try to remove the preamble that contains the error name and 1245 | * message to just get the stack trace. 1246 | */ 1247 | if (lines.length > 0 && lines[0].includes(STACK_TRACE_MARKER)) { 1248 | lines = lines.splice(1); 1249 | } 1250 | 1251 | trace = lines.join("\n"); 1252 | } else { 1253 | trace = "This JavaScript runtime does not expose stack trace information."; 1254 | } 1255 | 1256 | return toJavaString(trace); 1257 | } 1258 | 1259 | function gen_call_stack() { 1260 | return formatStackTrace(genBacktrace()); 1261 | } 1262 | 1263 | 1264 | /* 1265 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 1266 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1267 | * 1268 | * This code is free software; you can redistribute it and/or modify it 1269 | * under the terms of the GNU General Public License version 2 only, as 1270 | * published by the Free Software Foundation. Oracle designates this 1271 | * particular file as subject to the "Classpath" exception as provided 1272 | * by Oracle in the LICENSE file that accompanied this code. 1273 | * 1274 | * This code is distributed in the hope that it will be useful, but WITHOUT 1275 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1276 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1277 | * version 2 for more details (a copy is included in the LICENSE file that 1278 | * accompanied this code). 1279 | * 1280 | * You should have received a copy of the GNU General Public License version 1281 | * 2 along with this work; if not, write to the Free Software Foundation, 1282 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1283 | * 1284 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1285 | * or visit www.oracle.com if you need additional information or have any 1286 | * questions. 1287 | */ 1288 | 1289 | /** 1290 | * Provides the current working directory from the Config instance to Java code. 1291 | */ 1292 | function getCurrentWorkingDirectory() { 1293 | return toJavaString(runtime.data.config.currentWorkingDirectory); 1294 | } 1295 | runtime.setEndianness(1); 1296 | wasmImports.interop = { 1297 | 'Date.now' : (...args) => Date.now(...args), 1298 | 'formatStackTrace' : (...args) => formatStackTrace(...args), 1299 | 'genBacktrace' : (...args) => genBacktrace(...args), 1300 | 'getCurrentWorkingDirectory' : (...args) => getCurrentWorkingDirectory(...args), 1301 | 'llog' : (...args) => llog(...args), 1302 | 'performance.now' : (...args) => performance.now(...args), 1303 | 'runtime.setExitCode' : (...args) => runtime.setExitCode(...args), 1304 | 'stderrWriter.flush' : (...args) => stderrWriter.flush(...args), 1305 | 'stderrWriter.printChars' : (...args) => stderrWriter.printChars(...args), 1306 | 'stdoutWriter.flush' : (...args) => stdoutWriter.flush(...args), 1307 | 'stdoutWriter.printChars' : (...args) => stdoutWriter.printChars(...args), 1308 | } 1309 | ; 1310 | wasmImports.jsbody = { 1311 | '_Browser.addEventListener___JSObject_String_Callback_V' : (...args) => (function(element,eventType,handler){ 1312 | try{ 1313 | element.addEventListener(eventType, handler); 1314 | }catch( e ) { 1315 | conversion.handleJSError(e);}}).call(...args), 1316 | '_Browser.appendChild___JSObject_JSObject_V' : (...args) => (function(parent,child){ 1317 | try{ 1318 | return parent.appendChild(child); 1319 | }catch( e ) { 1320 | conversion.handleJSError(e);}}).call(...args), 1321 | '_Browser.createElement___String_JSObject' : (...args) => (function(tag){ 1322 | try{ 1323 | return document.createElement(tag); 1324 | }catch( e ) { 1325 | conversion.handleJSError(e);}}).call(...args), 1326 | '_Browser.globalThis___JSObject' : (...args) => (function(){ 1327 | try{ 1328 | return globalThis; 1329 | }catch( e ) { 1330 | conversion.handleJSError(e);}}).call(...args), 1331 | '_Browser.querySelector___String_JSObject' : (...args) => (function(selector){ 1332 | try{ 1333 | return document.querySelector(selector); 1334 | }catch( e ) { 1335 | conversion.handleJSError(e);}}).call(...args), 1336 | '_Browser.sink___Object_V' : (...args) => (function(o){ 1337 | try{ 1338 | 1339 | }catch( e ) { 1340 | conversion.handleJSError(e);}}).call(...args), 1341 | '_JSBigInt.javaString___String' : (...args) => (function(){ 1342 | try{ 1343 | return conversion.toProxy(toJavaString(this.toString())); 1344 | }catch( e ) { 1345 | conversion.handleJSError(e);}}).call(...args), 1346 | '_JSBoolean.javaBoolean___Boolean' : (...args) => (function(){ 1347 | try{ 1348 | return conversion.toProxy(conversion.createJavaBoolean(this)); 1349 | }catch( e ) { 1350 | conversion.handleJSError(e);}}).call(...args), 1351 | '_JSConversion.asJavaObjectOrString___Object_Object' : (...args) => (function(obj){ 1352 | try{ 1353 | return conversion.isInternalJavaObject(obj) ? obj : toJavaString(obj.toString()); 1354 | }catch( e ) { 1355 | conversion.handleJSError(e);}}).call(...args), 1356 | '_JSConversion.extractJavaScriptProxy___Object_Object' : (...args) => (function(self){ 1357 | try{ 1358 | return conversion.toProxy(self); 1359 | }catch( e ) { 1360 | conversion.handleJSError(e);}}).call(...args), 1361 | '_JSConversion.extractJavaScriptString___String_Object' : (...args) => (function(s){ 1362 | try{ 1363 | return conversion.extractJavaScriptString(s); 1364 | }catch( e ) { 1365 | conversion.handleJSError(e);}}).call(...args), 1366 | '_JSConversion.javaScriptToJava___Object_Object' : (...args) => (function(x){ 1367 | try{ 1368 | return conversion.javaScriptToJava(x); 1369 | }catch( e ) { 1370 | conversion.handleJSError(e);}}).call(...args), 1371 | '_JSConversion.javaScriptUndefined___Object' : (...args) => (function(){ 1372 | try{ 1373 | return undefined; 1374 | }catch( e ) { 1375 | conversion.handleJSError(e);}}).call(...args), 1376 | '_JSConversion.unproxy___Object_Object' : (...args) => (function(proxy){ 1377 | try{ 1378 | const javaNative = proxy[runtime.symbol.javaNative]; return javaNative === undefined ? null : javaNative; 1379 | }catch( e ) { 1380 | conversion.handleJSError(e);}}).call(...args), 1381 | '_JSNumber.javaDouble___Double' : (...args) => (function(){ 1382 | try{ 1383 | return conversion.toProxy(conversion.createJavaDouble(this)); 1384 | }catch( e ) { 1385 | conversion.handleJSError(e);}}).call(...args), 1386 | '_JSObject.call___Object$Ljava_lang_Object__Object' : (...args) => (function(thisArg,args){ 1387 | try{ 1388 | return this.apply(thisArg, conversion.extractJavaScriptArray(args[runtime.symbol.javaNative])); 1389 | }catch( e ) { 1390 | conversion.handleJSError(e);}}).call(...args), 1391 | '_JSObject.extractFacadeClass__Ljava_lang_Class__Object' : (...args) => (function(cls){ 1392 | try{ 1393 | return conversion.tryExtractFacadeClass(this, cls); 1394 | }catch( e ) { 1395 | conversion.handleJSError(e);}}).call(...args), 1396 | '_JSObject.get___Object_Object' : (...args) => (function(key){ 1397 | try{ 1398 | return this[key]; 1399 | }catch( e ) { 1400 | conversion.handleJSError(e);}}).call(...args), 1401 | '_JSObject.set___Object_Object_V' : (...args) => (function(key,newValue){ 1402 | try{ 1403 | this[key] = newValue; 1404 | }catch( e ) { 1405 | conversion.handleJSError(e);}}).call(...args), 1406 | '_JSObject.stringValue___String' : (...args) => (function(){ 1407 | try{ 1408 | return conversion.toProxy(toJavaString(this.toString())); 1409 | }catch( e ) { 1410 | conversion.handleJSError(e);}}).call(...args), 1411 | '_JSObject.typeofString___JSString' : (...args) => (function(){ 1412 | try{ 1413 | return typeof this; 1414 | }catch( e ) { 1415 | conversion.handleJSError(e);}}).call(...args), 1416 | '_JSString.javaString___String' : (...args) => (function(){ 1417 | try{ 1418 | return conversion.toProxy(toJavaString(this)); 1419 | }catch( e ) { 1420 | conversion.handleJSError(e);}}).call(...args), 1421 | '_JSString.of___String_JSString' : (...args) => (function(s){ 1422 | try{ 1423 | return conversion.extractJavaScriptString(s[runtime.symbol.javaNative]); 1424 | }catch( e ) { 1425 | conversion.handleJSError(e);}}).call(...args), 1426 | '_JSSymbol.javaString___String' : (...args) => (function(){ 1427 | try{ 1428 | return conversion.toProxy(toJavaString(this.toString())); 1429 | }catch( e ) { 1430 | conversion.handleJSError(e);}}).call(...args), 1431 | '_JSSymbol.referenceEquals___JSSymbol_JSSymbol_JSBoolean' : (...args) => (function(sym0,sym1){ 1432 | try{ 1433 | return sym0 === sym1; 1434 | }catch( e ) { 1435 | conversion.handleJSError(e);}}).call(...args), 1436 | } 1437 | ; 1438 | 1439 | 1440 | /* 1441 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 1442 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1443 | * 1444 | * This code is free software; you can redistribute it and/or modify it 1445 | * under the terms of the GNU General Public License version 2 only, as 1446 | * published by the Free Software Foundation. Oracle designates this 1447 | * particular file as subject to the "Classpath" exception as provided 1448 | * by Oracle in the LICENSE file that accompanied this code. 1449 | * 1450 | * This code is distributed in the hope that it will be useful, but WITHOUT 1451 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1452 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1453 | * version 2 for more details (a copy is included in the LICENSE file that 1454 | * accompanied this code). 1455 | * 1456 | * You should have received a copy of the GNU General Public License version 1457 | * 2 along with this work; if not, write to the Free Software Foundation, 1458 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1459 | * 1460 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1461 | * or visit www.oracle.com if you need additional information or have any 1462 | * questions. 1463 | */ 1464 | 1465 | /** 1466 | * Code dealing with moving values between the Java and JavaScript world. 1467 | * 1468 | * This code handles Java values: 1469 | * - All Java values except for objects and longs are represented as JS Number values. 1470 | * - Object and long representation depends on the backend used. 1471 | * Variables and arguments representing Java values are usually marked explicitly 1472 | * (e.g. by being named something like javaObject or jlstring, which stands for 1473 | * java.lang.String). 1474 | * 1475 | * Java values can be used to call Java methods directly without additional 1476 | * conversions, which is the basis of the functionality this class provides. 1477 | * It facilitates calling Java methods from JavaScript by first performing the 1478 | * necessary conversions or coercions before the Java call is executed. In the 1479 | * reverse direction, it helps Java code execute JS code (e.g. through the @JS 1480 | * annotation) by converting or coercing Java values into appropriate JS values. 1481 | */ 1482 | class Conversion { 1483 | /** 1484 | * Associates the given Java object with the given JS value. 1485 | */ 1486 | setJavaScriptNative(javaObject, jsNative) { 1487 | throw new Error("Unimplemented: Conversion.javaScriptNative"); 1488 | } 1489 | 1490 | /** 1491 | * Returns the JS value associated with the given Java object or null if there is no associated value. 1492 | */ 1493 | extractJavaScriptNative(javaObject) { 1494 | throw new Error("Unimplemented: Conversion.extractJavaScriptNative"); 1495 | } 1496 | 1497 | // Java-to-JavaScript conversions 1498 | 1499 | /** 1500 | * Given a Java boxed Double, creates the corresponding JavaScript number value. 1501 | * 1502 | * @param jldouble The java.lang.Double object 1503 | * @return {*} A JavaScript Number value 1504 | */ 1505 | extractJavaScriptNumber(jldouble) { 1506 | throw new Error("Unimplemented: Conversion.extractJavaScriptNumber"); 1507 | } 1508 | 1509 | /** 1510 | * Given a Java String, creates the corresponding JavaScript string value. 1511 | * 1512 | * Note: the Java method called in this implementation will return (in the generated code) 1513 | * an actual primitive Java string. 1514 | * 1515 | * @param jlstring The java.lang.String object 1516 | * @return {*} A JavaScript String value 1517 | */ 1518 | extractJavaScriptString(jlstring) { 1519 | throw new Error("Unimplemented: Conversion.extractJavaScriptString"); 1520 | } 1521 | 1522 | /** 1523 | * Converts a Java array to a JavaScript array that contains JavaScript values 1524 | * that correspond to the Java values of the input array. 1525 | * 1526 | * @param jarray A Java array 1527 | * @returns {*} The resulting JavaScript array 1528 | */ 1529 | extractJavaScriptArray(jarray) { 1530 | throw new Error("Unimplemented: Conversion.extractJavaScriptArray"); 1531 | } 1532 | 1533 | // JavaScript-to-Java conversions (standard Java classes) 1534 | 1535 | /** 1536 | * Creates a java.lang.Boolean object from a JavaScript boolean value. 1537 | */ 1538 | createJavaBoolean(b) { 1539 | throw new Error("Unimplemented: Conversion.createJavaBoolean"); 1540 | } 1541 | 1542 | /** 1543 | * Creates a java.lang.Byte object from a JavaScript number value. 1544 | */ 1545 | createJavaByte(x) { 1546 | throw new Error("Unimplemented: Conversion.createJavaByte"); 1547 | } 1548 | 1549 | /** 1550 | * Creates a java.lang.Short object from a JavaScript number value. 1551 | */ 1552 | createJavaShort(x) { 1553 | throw new Error("Unimplemented: Conversion.createJavaShort"); 1554 | } 1555 | 1556 | /** 1557 | * Creates a java.lang.Character object from a JavaScript number value. 1558 | */ 1559 | createJavaCharacter(x) { 1560 | throw new Error("Unimplemented: Conversion.createJavaCharacter"); 1561 | } 1562 | 1563 | /** 1564 | * Creates a java.lang.Integer object from a JavaScript number value. 1565 | */ 1566 | createJavaInteger(x) { 1567 | throw new Error("Unimplemented: Conversion.createJavaInteger"); 1568 | } 1569 | 1570 | /** 1571 | * Creates a java.lang.Float object from a JavaScript number value. 1572 | */ 1573 | createJavaFloat(x) { 1574 | throw new Error("Unimplemented: Conversion.createJavaFloat"); 1575 | } 1576 | 1577 | /** 1578 | * Creates a java.lang.Long object from a JavaScript number value. 1579 | */ 1580 | createJavaLong(x) { 1581 | throw new Error("Unimplemented: Conversion.createJavaLong"); 1582 | } 1583 | 1584 | /** 1585 | * Creates a java.lang.Double object from a JavaScript number value. 1586 | */ 1587 | createJavaDouble(x) { 1588 | throw new Error("Unimplemented: Conversion.createJavaDouble"); 1589 | } 1590 | 1591 | /** 1592 | * Gets the JavaKind ordinal for the given hub, as expected by `boxIfNeeded`. 1593 | */ 1594 | getHubKindOrdinal(hub) { 1595 | throw new Error("Unimplemented: Conversion.getHubKindOrdinal"); 1596 | } 1597 | 1598 | /** 1599 | * Box the given value if the specified type is primitive. 1600 | * 1601 | * The parameter type is the enum index as defined in jdk.vm.ci.meta.JavaKind. 1602 | * The following is a summary: 1603 | * 1604 | * 0 - Boolean 1605 | * 1 - Byte 1606 | * 2 - Short 1607 | * 3 - Char 1608 | * 4 - Int 1609 | * 5 - Float 1610 | * 6 - Long 1611 | * 7 - Double 1612 | * 8 - Object 1613 | * 1614 | * @param {number=} type 1615 | */ 1616 | boxIfNeeded(javaValue, type) { 1617 | switch (type) { 1618 | case 0: 1619 | return this.createJavaBoolean(javaValue); 1620 | case 1: 1621 | return this.createJavaByte(javaValue); 1622 | case 2: 1623 | return this.createJavaShort(javaValue); 1624 | case 3: 1625 | return this.createJavaCharacter(javaValue); 1626 | case 4: 1627 | return this.createJavaInteger(javaValue); 1628 | case 5: 1629 | return this.createJavaFloat(javaValue); 1630 | case 6: 1631 | return this.createJavaLong(javaValue); 1632 | case 7: 1633 | return this.createJavaDouble(javaValue); 1634 | default: 1635 | return javaValue; 1636 | } 1637 | } 1638 | 1639 | /** 1640 | * Unbox the given value if the specified type is primitive. 1641 | * 1642 | * See documentation for `boxIfNeeded`. 1643 | */ 1644 | unboxIfNeeded(javaObject, type) { 1645 | switch (type) { 1646 | case 0: 1647 | return this.unboxBoolean(javaObject); 1648 | case 1: 1649 | return this.unboxByte(javaObject); 1650 | case 2: 1651 | return this.unboxShort(javaObject); 1652 | case 3: 1653 | return this.unboxChar(javaObject); 1654 | case 4: 1655 | return this.unboxInt(javaObject); 1656 | case 5: 1657 | return this.unboxFloat(javaObject); 1658 | case 6: 1659 | return this.unboxLong(javaObject); 1660 | case 7: 1661 | return this.unboxDouble(javaObject); 1662 | default: 1663 | return javaObject; 1664 | } 1665 | } 1666 | 1667 | unboxBoolean(jlBoolean) { 1668 | throw new Error("Unimplemented: Conversion.unboxBoolean"); 1669 | } 1670 | 1671 | unboxByte(jlByte) { 1672 | throw new Error("Unimplemented: Conversion.unboxByte"); 1673 | } 1674 | 1675 | unboxShort(jlShort) { 1676 | throw new Error("Unimplemented: Conversion.unboxShort"); 1677 | } 1678 | 1679 | unboxChar(jlChar) { 1680 | throw new Error("Unimplemented: Conversion.unboxChar"); 1681 | } 1682 | 1683 | unboxInt(jlInt) { 1684 | throw new Error("Unimplemented: Conversion.unboxInt"); 1685 | } 1686 | 1687 | unboxFloat(jlFloat) { 1688 | throw new Error("Unimplemented: Conversion.unboxFloat"); 1689 | } 1690 | 1691 | unboxLong(jlLong) { 1692 | throw new Error("Unimplemented: Conversion.unboxLong"); 1693 | } 1694 | 1695 | unboxDouble(jlDouble) { 1696 | throw new Error("Unimplemented: Conversion.unboxDouble"); 1697 | } 1698 | 1699 | /** 1700 | * Gets the boxed counterpart of the given primitive hub. 1701 | */ 1702 | getBoxedHub(jlClass) { 1703 | throw new Error("Unimplemented: Conversion.getBoxedHub"); 1704 | } 1705 | 1706 | // JavaScript-to-Java conversions (JSValue classes) 1707 | 1708 | /** 1709 | * Gets the Java singleton object that represents the JavaScript undefined value. 1710 | */ 1711 | createJSUndefined() { 1712 | throw new Error("Unimplemented: Conversion.createJSUndefined"); 1713 | } 1714 | 1715 | /** 1716 | * Wraps a JavaScript Boolean into a Java JSBoolean object. 1717 | * 1718 | * @param boolean The JavaScript boolean to wrap 1719 | * @return {*} The Java JSBoolean object 1720 | */ 1721 | createJSBoolean(boolean) { 1722 | throw new Error("Unimplemented: Conversion.createJSBoolean"); 1723 | } 1724 | 1725 | /** 1726 | * Wraps a JavaScript Number into a Java JSNumber object. 1727 | * 1728 | * @param number The JavaScript number to wrap 1729 | * @return {*} The Java JSNumber object 1730 | */ 1731 | createJSNumber(number) { 1732 | throw new Error("Unimplemented: Conversion.createJSNumber"); 1733 | } 1734 | 1735 | /** 1736 | * Wraps a JavaScript BigInt into a Java JSBigInt object. 1737 | * 1738 | * @param bigint The JavaScript BigInt value to wrap 1739 | * @return {*} The Java JSBigInt object 1740 | */ 1741 | createJSBigInt(bigint) { 1742 | throw new Error("Unimplemented: Conversion.createJSBigInt"); 1743 | } 1744 | 1745 | /** 1746 | * Wraps a JavaScript String into a Java JSString object. 1747 | * 1748 | * @param string The JavaScript String value to wrap 1749 | * @return {*} The Java JSString object 1750 | */ 1751 | createJSString(string) { 1752 | throw new Error("Unimplemented: Conversion.createJSString"); 1753 | } 1754 | 1755 | /** 1756 | * Wraps a JavaScript Symbol into a Java JSSymbol object. 1757 | * 1758 | * @param symbol The JavaScript Symbol value to wrap 1759 | * @return {*} The Java JSSymbol object 1760 | */ 1761 | createJSSymbol(symbol) { 1762 | throw new Error("Unimplemented: Conversion.createJSSymbol"); 1763 | } 1764 | 1765 | /** 1766 | * Wraps a JavaScript object into a Java JSObject object. 1767 | * 1768 | * @param obj The JavaScript Object value to wrap 1769 | * @returns {*} The Java JSObject object 1770 | */ 1771 | createJSObject(obj) { 1772 | throw new Error("Unimplemented: Conversion.createJSObject"); 1773 | } 1774 | 1775 | // Helper methods 1776 | 1777 | /** 1778 | * Checks if the specified object (which may be a JavaScript value or a Java value) is an internal Java object. 1779 | */ 1780 | isInternalJavaObject(obj) { 1781 | throw new Error("Unimplemented: Conversion.isInternalJavaObject"); 1782 | } 1783 | 1784 | isPrimitiveHub(hub) { 1785 | throw new Error("Unimplemented: Conversion.isPrimitiveHub"); 1786 | } 1787 | 1788 | isJavaLangString(obj) { 1789 | throw new Error("Unimplemented: Conversion.isJavaLangString"); 1790 | } 1791 | 1792 | isJavaLangClass(obj) { 1793 | throw new Error("Unimplemented: Conversion.isJavaLangClassHub"); 1794 | } 1795 | 1796 | isInstance(obj, hub) { 1797 | throw new Error("Unimplemented: Conversion.isInstance"); 1798 | } 1799 | 1800 | /** 1801 | * Copies own fields from source to destination. 1802 | * 1803 | * Existing fields in the destination are overwritten. 1804 | */ 1805 | copyOwnFields(src, dst) { 1806 | for (let name of Object.getOwnPropertyNames(src)) { 1807 | dst[name] = src[name]; 1808 | } 1809 | } 1810 | 1811 | /** 1812 | * Creates an anonymous JavaScript object, and does the mirror handshake. 1813 | */ 1814 | createAnonymousJavaScriptObject() { 1815 | const x = {}; 1816 | const jsObject = this.createJSObject(x); 1817 | x[runtime.symbol.javaNative] = jsObject; 1818 | return x; 1819 | } 1820 | 1821 | /** 1822 | * Obtains or creates the proxy handler for the given Java class 1823 | */ 1824 | getOrCreateProxyHandler(arg) { 1825 | throw new Error("Unimplemented: Conversion.getOrCreateProxyHandler"); 1826 | } 1827 | 1828 | /** 1829 | * For proxying the given object returns value that should be passed to 1830 | * getOrCreateProxyHandler. 1831 | */ 1832 | _getProxyHandlerArg(obj) { 1833 | throw new Error("Unimplemented: Conversion._getProxyHandlerArg"); 1834 | } 1835 | 1836 | /** 1837 | * Creates a proxy that intercepts messages that correspond to Java method calls and Java field accesses. 1838 | * 1839 | * @param obj The Java object to create a proxy for 1840 | * @return {*} The proxy around the Java object 1841 | */ 1842 | toProxy(obj) { 1843 | let proxyHandler = this.getOrCreateProxyHandler(this._getProxyHandlerArg(obj)); 1844 | // The wrapper is a temporary object that allows having the non-identifier name of the target function. 1845 | // We declare the property as a function, to ensure that it is constructable, so that the Proxy handler's construct method is callable. 1846 | let targetWrapper = { 1847 | ["Java Proxy"]: function (key) { 1848 | if (key === runtime.symbol.javaNative) { 1849 | return obj; 1850 | } 1851 | return undefined; 1852 | }, 1853 | }; 1854 | 1855 | return new Proxy(targetWrapper["Java Proxy"], proxyHandler); 1856 | } 1857 | 1858 | /** 1859 | * Converts a JavaScript value to the corresponding Java representation. 1860 | * 1861 | * The exact rules of the mapping are documented in the Java JS annotation class. 1862 | * 1863 | * This method is only meant to be called from the conversion code generated for JS-annotated methods. 1864 | * 1865 | * @param x The JavaScript value to convert 1866 | * @return {*} The Java representation of the JavaScript value 1867 | */ 1868 | javaScriptToJava(x) { 1869 | // Step 1: check null, which is mapped 1:1 to null in Java. 1870 | if (x === null) { 1871 | return null; 1872 | } 1873 | 1874 | // Step 2: check undefined, which is a singleton in Java. 1875 | if (x === undefined) { 1876 | return this.createJSUndefined(); 1877 | } 1878 | 1879 | // Step 3: check if the javaNative property is set. 1880 | // This covers objects that already have Java counterparts (for example, Java proxies). 1881 | const javaValue = x[runtime.symbol.javaNative]; 1882 | if (javaValue !== undefined) { 1883 | return javaValue; 1884 | } 1885 | 1886 | // Step 4: use the JavaScript type to select the appropriate Java representation. 1887 | const tpe = typeof x; 1888 | switch (tpe) { 1889 | case "boolean": 1890 | return this.createJSBoolean(x); 1891 | case "number": 1892 | return this.createJSNumber(x); 1893 | case "bigint": 1894 | return this.createJSBigInt(x); 1895 | case "string": 1896 | return this.createJSString(x); 1897 | case "symbol": 1898 | return this.createJSSymbol(x); 1899 | case "object": 1900 | case "function": 1901 | // We know this is a normal object created in JavaScript, 1902 | // otherwise it would have a runtime.symbol.javaNative property, 1903 | // and the conversion would have returned in Step 3. 1904 | return this.createJSObject(x); 1905 | default: 1906 | throw new Error("unexpected type: " + tpe); 1907 | } 1908 | } 1909 | 1910 | /** 1911 | * Maps each JavaScript value in the input array to a Java value. 1912 | * See {@code javaScriptToJava}. 1913 | */ 1914 | eachJavaScriptToJava(javaScriptValues) { 1915 | const javaValues = new Array(javaScriptValues.length); 1916 | for (let i = 0; i < javaScriptValues.length; i++) { 1917 | javaValues[i] = this.javaScriptToJava(javaScriptValues[i]); 1918 | } 1919 | return javaValues; 1920 | } 1921 | 1922 | /** 1923 | * Converts a Java value to JavaScript. 1924 | */ 1925 | javaToJavaScript(x) { 1926 | throw new Error("Unimplemented: Conversion.javaToJavaScript"); 1927 | } 1928 | 1929 | throwClassCastExceptionImpl(javaObject, tpeNameJavaString) { 1930 | throw new Error("Unimplemented: Conversion.throwClassCastExceptionImpl"); 1931 | } 1932 | 1933 | throwClassCastException(javaObject, tpe) { 1934 | let tpeName; 1935 | if (typeof tpe === "string") { 1936 | tpeName = tpe; 1937 | } else if (typeof tpe === "function") { 1938 | tpeName = tpe.name; 1939 | } else { 1940 | tpeName = tpe.toString(); 1941 | } 1942 | this.throwClassCastExceptionImpl(javaObject, toJavaString(tpeName)); 1943 | } 1944 | 1945 | /** 1946 | * Converts the specified Java Proxy to the target JavaScript type, if possible. 1947 | * 1948 | * This method is meant to be called from Java Proxy object, either when implicit coercion is enabled, 1949 | * or when the user explicitly invokes coercion on the Proxy object. 1950 | * 1951 | * @param proxyHandler handler for the proxy that must be converted 1952 | * @param proxy the Java Proxy object that should be coerced 1953 | * @param tpe target JavaScript type name (result of the typeof operator) or constructor function 1954 | * @return {*} the resulting JavaScript value 1955 | */ 1956 | coerceJavaProxyToJavaScriptType(proxyHandler, proxy, tpe) { 1957 | throw new Error("Unimplemented: Conversion.coerceJavaProxyToJavaScriptType"); 1958 | } 1959 | 1960 | /** 1961 | * Try to convert the JavaScript object to a Java facade class, or return null. 1962 | * 1963 | * @param obj JavaScript object whose Java facade class we search for 1964 | * @param cls target Java class in the form of its JavaScript counterpart 1965 | * @return {*} the mirror instance wrapped into a JavaScript Java Proxy, or null 1966 | */ 1967 | tryExtractFacadeClass(obj, cls) { 1968 | const facades = runtime.findFacadesFor(obj.constructor); 1969 | const rawJavaHub = cls[runtime.symbol.javaNative]; 1970 | const internalJavaClass = rawJavaHub[runtime.symbol.jsClass]; 1971 | if (facades.has(internalJavaClass)) { 1972 | const rawJavaMirror = new internalJavaClass(); 1973 | // Note: only one-way handshake, since the JavaScript object could be recast to a different Java facade class. 1974 | this.setJavaScriptNative(rawJavaMirror, obj); 1975 | return this.toProxy(rawJavaMirror); 1976 | } else { 1977 | return null; 1978 | } 1979 | } 1980 | 1981 | /** 1982 | * Coerce the specified JavaScript value to the specified Java type. 1983 | * 1984 | * See VM.as for the specification of this function. 1985 | */ 1986 | coerceJavaScriptToJavaType(javaScriptValue, type) { 1987 | throw new Error("Unimplemented: Conversion.coerceJavaScriptToJavaType"); 1988 | } 1989 | } 1990 | 1991 | /** 1992 | * Handle for proxying Java objects. 1993 | * 1994 | * Client JS code never directly sees Java object, instead they see proxies 1995 | * using this handler. The handler is generally specialized per type. 1996 | * It provides access to the underlying Java methods. 1997 | * 1998 | * It also supports invoking the proxy, which calls the single abstract method 1999 | * in the Java object if available, and for Class objects the new operator 2000 | * works, creating a Java object and invoking a matching constructor. 2001 | * 2002 | * The backends provide method metadata describing the Java methods available 2003 | * to the proxy. At runtime, when a method call is triggered (a method is 2004 | * accessed and called, the proxy itself is invoked, or a constructor is called), 2005 | * the proxy will find a matching implementation based on the types of the 2006 | * passed arguments. 2007 | * Arguments and return values are automatically converted to and from Java 2008 | * objects respectively, but no coercion is done. 2009 | */ 2010 | class ProxyHandler { 2011 | constructor() { 2012 | this._initialized = false; 2013 | this._methods = {}; 2014 | this._staticMethods = {}; 2015 | this._javaConstructorMethod = null; 2016 | } 2017 | 2018 | ensureInitialized() { 2019 | if (!this._initialized) { 2020 | this._initialized = true; 2021 | // Function properties derived from accessible Java methods. 2022 | this._createProxyMethods(); 2023 | // Default function properties. 2024 | this._createDefaultMethods(); 2025 | } 2026 | } 2027 | 2028 | _getMethods() { 2029 | this.ensureInitialized(); 2030 | return this._methods; 2031 | } 2032 | 2033 | _getStaticMethods() { 2034 | this.ensureInitialized(); 2035 | return this._staticMethods; 2036 | } 2037 | 2038 | _getJavaConstructorMethod() { 2039 | this.ensureInitialized(); 2040 | return this._javaConstructorMethod; 2041 | } 2042 | 2043 | /** 2044 | * Returns a ClassMetadata instance for the class this proxy handler represents. 2045 | */ 2046 | _getClassMetadata() { 2047 | throw new Error("Unimplemented: ProxyHandler._getClassMetadata"); 2048 | } 2049 | 2050 | _getMethodTable() { 2051 | const classMeta = this._getClassMetadata(); 2052 | if (classMeta === undefined) { 2053 | return undefined; 2054 | } 2055 | return classMeta.methodTable; 2056 | } 2057 | 2058 | /** 2059 | * String that can be printed as part of the toString and valueOf functions. 2060 | */ 2061 | _getClassName() { 2062 | throw new Error("Unimplemented: ProxyHandler._getClassName"); 2063 | } 2064 | 2065 | /** 2066 | * Link the methods object to the prototype chain of the methods object of the superclass' proxy handler. 2067 | */ 2068 | _linkMethodPrototype() { 2069 | throw new Error("Unimplemented: ProxyHandler._linkMethodPrototype"); 2070 | } 2071 | 2072 | _createProxyMethods() { 2073 | // Create proxy methods for the current class. 2074 | const methodTable = this._getMethodTable(); 2075 | if (methodTable === undefined) { 2076 | return; 2077 | } 2078 | 2079 | const proxyHandlerThis = this; 2080 | for (const name in methodTable) { 2081 | const overloads = methodTable[name]; 2082 | const instanceOverloads = []; 2083 | const staticOverloads = []; 2084 | for (const m of overloads) { 2085 | if (m.isStatic) { 2086 | staticOverloads.push(m); 2087 | } else { 2088 | instanceOverloads.push(m); 2089 | } 2090 | } 2091 | if (instanceOverloads.length > 0) { 2092 | this._methods[name] = function (...javaScriptArgs) { 2093 | // Note: the 'this' value is bound to the Proxy object. 2094 | return proxyHandlerThis._invokeProxyMethod(name, instanceOverloads, this, ...javaScriptArgs); 2095 | }; 2096 | } 2097 | if (staticOverloads.length > 0) { 2098 | this._staticMethods[name] = function (...javaScriptArgs) { 2099 | // Note: the 'this' value is bound to the Proxy object. 2100 | return proxyHandlerThis._invokeProxyMethod(name, staticOverloads, null, ...javaScriptArgs); 2101 | }; 2102 | } 2103 | } 2104 | if (methodTable[runtime.symbol.ctor] !== undefined) { 2105 | const overloads = methodTable[runtime.symbol.ctor]; 2106 | this._javaConstructorMethod = function (javaScriptJavaProxy, ...javaScriptArgs) { 2107 | // Note: the 'this' value is bound to the Proxy object. 2108 | return proxyHandlerThis._invokeProxyMethod("", overloads, javaScriptJavaProxy, ...javaScriptArgs); 2109 | }; 2110 | } else { 2111 | this._javaConstructorMethod = function (javaScriptJavaProxy, ...javaScriptArgs) { 2112 | throw new Error( 2113 | "Cannot invoke the constructor. Make sure that the constructors are explicitly added to the image." 2114 | ); 2115 | }; 2116 | } 2117 | 2118 | this._linkMethodPrototype(); 2119 | } 2120 | 2121 | /** 2122 | * Checks whether the given argument values can be used to call the method identified by the given metdata class. 2123 | */ 2124 | _conforms(args, metadata) { 2125 | if (metadata.paramHubs.length !== args.length) { 2126 | return false; 2127 | } 2128 | for (let i = 0; i < args.length; i++) { 2129 | const arg = args[i]; 2130 | let paramHub = metadata.paramHubs[i]; 2131 | if (paramHub === null) { 2132 | // A null parameter hub means that the type-check always passes. 2133 | continue; 2134 | } 2135 | if (conversion.isPrimitiveHub(paramHub)) { 2136 | // A primitive hub must be replaced with the hub of the corresponding boxed type. 2137 | paramHub = conversion.getBoxedHub(paramHub); 2138 | } 2139 | if (!conversion.isInstance(arg, paramHub)) { 2140 | return false; 2141 | } 2142 | } 2143 | return true; 2144 | } 2145 | 2146 | _unboxJavaArguments(args, metadata) { 2147 | // Precondition -- method metadata refers to a method with a correct arity. 2148 | for (let i = 0; i < args.length; i++) { 2149 | const paramHub = metadata.paramHubs[i]; 2150 | args[i] = conversion.unboxIfNeeded(args[i], conversion.getHubKindOrdinal(paramHub)); 2151 | } 2152 | } 2153 | 2154 | _createDefaultMethods() { 2155 | if (!this._methods.hasOwnProperty("toString")) { 2156 | // The check must use hasOwnProperty, because toString always exists in the prototype. 2157 | this._methods["toString"] = () => "[Java Proxy: " + this._getClassName() + "]"; 2158 | } else { 2159 | const javaToString = this._methods["toString"]; 2160 | this._methods[runtime.symbol.toString] = javaToString; 2161 | this._methods["toString"] = function () { 2162 | // The `this` value must be bound to the proxy instance. 2163 | // 2164 | // The `toString` method is used often in JavaScript, and treated specially. 2165 | // If its return type is a Java String, then that string is converted to a JavaScript string. 2166 | // In other words, if the result of the call is a JavaScript proxy (see _invokeProxyMethod return value), 2167 | // then proxies that represent java.lang.String are converted to JavaScript strings. 2168 | const javaScriptResult = javaToString.call(this); 2169 | if (typeof javaScriptResult === "function" || typeof javaScriptResult === "object") { 2170 | const javaResult = javaScriptResult[runtime.symbol.javaNative]; 2171 | if (javaResult !== undefined && conversion.isJavaLangString(javaResult)) { 2172 | return conversion.extractJavaScriptString(javaResult); 2173 | } else { 2174 | return javaScriptResult; 2175 | } 2176 | } else { 2177 | return javaScriptResult; 2178 | } 2179 | }; 2180 | } 2181 | 2182 | // Override Java methods that return valueOf. 2183 | // JavaScript requires that valueOf returns a JavaScript primitive (in this case, string). 2184 | this._methods["valueOf"] = () => "[Java Proxy: " + this._getClassName() + "]"; 2185 | 2186 | const proxyHandlerThis = this; 2187 | const asProperty = function (tpe) { 2188 | // Note: this will be bound to the Proxy object. 2189 | return conversion.coerceJavaProxyToJavaScriptType(proxyHandlerThis, this, tpe); 2190 | }; 2191 | if (!("$as" in this._methods)) { 2192 | this._methods["$as"] = asProperty; 2193 | } 2194 | this._methods[runtime.symbol.javaScriptCoerceAs] = asProperty; 2195 | 2196 | const vmProperty = vm; 2197 | if (!("$vm" in this._methods)) { 2198 | this._methods["$vm"] = vmProperty; 2199 | } 2200 | } 2201 | 2202 | _loadMethod(target, key) { 2203 | const member = this._getMethods()[key]; 2204 | if (member !== undefined) { 2205 | return member; 2206 | } 2207 | } 2208 | 2209 | _methodNames() { 2210 | return Object.keys(this._getMethods()); 2211 | } 2212 | 2213 | _invokeProxyMethod(name, overloads, javaScriptJavaProxy, ...javaScriptArgs) { 2214 | // For static methods, javaScriptThis is set to null. 2215 | const isStatic = javaScriptJavaProxy === null; 2216 | const javaThis = isStatic ? null : javaScriptJavaProxy[runtime.symbol.javaNative]; 2217 | const javaArgs = conversion.eachJavaScriptToJava(javaScriptArgs); 2218 | for (let i = 0; i < overloads.length; i++) { 2219 | const metadata = overloads[i]; 2220 | if (this._conforms(javaArgs, metadata)) { 2221 | // Where necessary, perform unboxing of Java arguments. 2222 | this._unboxJavaArguments(javaArgs, metadata); 2223 | let javaResult; 2224 | try { 2225 | if (isStatic) { 2226 | javaResult = metadata.method.call(null, ...javaArgs); 2227 | } else { 2228 | javaResult = metadata.method.call(null, javaThis, ...javaArgs); 2229 | } 2230 | } catch (error) { 2231 | throw conversion.javaToJavaScript(error); 2232 | } 2233 | if (javaResult === undefined) { 2234 | // This only happens when the return type is void. 2235 | return undefined; 2236 | } 2237 | // If necessary, box the Java return value. 2238 | const retHub = metadata.returnHub; 2239 | javaResult = conversion.boxIfNeeded(javaResult, conversion.getHubKindOrdinal(retHub)); 2240 | const javaScriptResult = conversion.javaToJavaScript(javaResult); 2241 | return javaScriptResult; 2242 | } 2243 | } 2244 | const methodName = name !== null ? "method '" + name + "'" : "single abstract method"; 2245 | throw new Error("No matching signature for " + methodName + " and argument list '" + javaScriptArgs + "'"); 2246 | } 2247 | 2248 | /** 2249 | * The Java type hierarchy is not modelled in the proxy and the proxied 2250 | * object has no prototype. 2251 | */ 2252 | getPrototypeOf(target) { 2253 | return null; 2254 | } 2255 | 2256 | /** 2257 | * Modifying the prototype of the proxied object is not allowed. 2258 | */ 2259 | setPrototypeOf(target, prototype) { 2260 | return false; 2261 | } 2262 | 2263 | /** 2264 | * Proxied objects are not extensible in any way. 2265 | */ 2266 | isExtensible(target) { 2267 | return false; 2268 | } 2269 | 2270 | /** 2271 | * We allow calling Object.preventExtensions on the proxy. 2272 | * However, it won't do anything, the proxy handler already prevents extensions. 2273 | */ 2274 | preventExtensions(target) { 2275 | return true; 2276 | } 2277 | 2278 | getOwnPropertyDescriptor(target, key) { 2279 | const value = this._loadMethod(target, key); 2280 | if (value === undefined) { 2281 | return undefined; 2282 | } 2283 | return { 2284 | value: value, 2285 | writable: false, 2286 | enumerable: false, 2287 | configurable: false, 2288 | }; 2289 | } 2290 | 2291 | /** 2292 | * Defining properties on the Java object is not allowed. 2293 | */ 2294 | defineProperty(target, key, descriptor) { 2295 | return false; 2296 | } 2297 | 2298 | has(target, key) { 2299 | return this._loadMethod(target, key) !== undefined; 2300 | } 2301 | 2302 | get(target, key) { 2303 | if (key === runtime.symbol.javaNative) { 2304 | return target(runtime.symbol.javaNative); 2305 | } else { 2306 | const javaObject = target(runtime.symbol.javaNative); 2307 | // TODO GR-60603 Deal with arrays in WasmGC backend 2308 | if (Array.isArray(javaObject) && typeof key === "string") { 2309 | const index = Number(key); 2310 | if (0 <= index && index < javaObject.length) { 2311 | return conversion.javaToJavaScript(javaObject[key]); 2312 | } else if (key === "length") { 2313 | return javaObject.length; 2314 | } 2315 | } 2316 | } 2317 | return this._loadMethod(target, key); 2318 | } 2319 | 2320 | set(target, key, value, receiver) { 2321 | const javaObject = target(runtime.symbol.javaNative); 2322 | // TODO GR-60603 Deal with arrays in WasmGC backend 2323 | if (Array.isArray(javaObject)) { 2324 | const index = Number(key); 2325 | if (0 <= index && index < javaObject.length) { 2326 | javaObject[key] = conversion.javaScriptToJava(value); 2327 | return true; 2328 | } 2329 | } 2330 | return false; 2331 | } 2332 | 2333 | /** 2334 | * Deleting properties on the Java object is not allowed. 2335 | */ 2336 | deleteProperty(target, key) { 2337 | return false; 2338 | } 2339 | 2340 | ownKeys(target) { 2341 | return this._methodNames(); 2342 | } 2343 | 2344 | apply(target, javaScriptThisArg, javaScriptArgs) { 2345 | // We need to convert the Proxy's target function to the Java Proxy. 2346 | const javaScriptJavaProxy = conversion.toProxy(target(runtime.symbol.javaNative)); 2347 | // Note: the JavaScript this argument for the apply method is never exposed to Java, so we just ignore it. 2348 | return this._applyWithObject(javaScriptJavaProxy, javaScriptArgs); 2349 | } 2350 | 2351 | _getSingleAbstractMethod(javaScriptJavaProxy) { 2352 | return this._getClassMetadata().singleAbstractMethod; 2353 | } 2354 | 2355 | _applyWithObject(javaScriptJavaProxy, javaScriptArgs) { 2356 | const sam = this._getSingleAbstractMethod(javaScriptJavaProxy); 2357 | if (sam === undefined) { 2358 | throw new Error("Java Proxy is not a functional interface, so 'apply' cannot be called from JavaScript."); 2359 | } 2360 | return this._invokeProxyMethod(null, [sam], javaScriptJavaProxy, ...javaScriptArgs); 2361 | } 2362 | 2363 | /** 2364 | * Create uninitialized instance of given Java type. 2365 | */ 2366 | _createInstance(hub) { 2367 | throw new Error("Unimplemented: ProxyHandler._createInstance"); 2368 | } 2369 | 2370 | construct(target, argumentsList) { 2371 | const javaThis = target(runtime.symbol.javaNative); 2372 | // This is supposed to be a proxy handler for java.lang.Class objects 2373 | // and javaThis is supposed to be some Class instance. 2374 | if (!conversion.isJavaLangClass(javaThis)) { 2375 | throw new Error( 2376 | "Cannot invoke the 'new' operator. The 'new' operator can only be used on Java Proxies that represent the 'java.lang.Class' type." 2377 | ); 2378 | } 2379 | // Allocate the Java object from Class instance 2380 | const javaInstance = this._createInstance(javaThis); 2381 | // Lookup constructor method of the target class. 2382 | // This proxy handler is for java.lang.Class while javaThis is a 2383 | // java.lang.Class instance for some object type for which we want to 2384 | // lookup the constructor. 2385 | const instanceProxyHandler = conversion.getOrCreateProxyHandler(conversion._getProxyHandlerArg(javaInstance)); 2386 | const javaConstructorMethod = instanceProxyHandler._getJavaConstructorMethod(); 2387 | // Convert the Java instance to JS (usually creates a proxy) 2388 | const javaScriptInstance = conversion.javaToJavaScript(javaInstance); 2389 | // Call the Java constructor method. 2390 | javaConstructorMethod(javaScriptInstance, ...argumentsList); 2391 | return javaScriptInstance; 2392 | } 2393 | } 2394 | 2395 | 2396 | /* 2397 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 2398 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2399 | * 2400 | * This code is free software; you can redistribute it and/or modify it 2401 | * under the terms of the GNU General Public License version 2 only, as 2402 | * published by the Free Software Foundation. Oracle designates this 2403 | * particular file as subject to the "Classpath" exception as provided 2404 | * by Oracle in the LICENSE file that accompanied this code. 2405 | * 2406 | * This code is distributed in the hope that it will be useful, but WITHOUT 2407 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 2408 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 2409 | * version 2 for more details (a copy is included in the LICENSE file that 2410 | * accompanied this code). 2411 | * 2412 | * You should have received a copy of the GNU General Public License version 2413 | * 2 along with this work; if not, write to the Free Software Foundation, 2414 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2415 | * 2416 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2417 | * or visit www.oracle.com if you need additional information or have any 2418 | * questions. 2419 | */ 2420 | 2421 | /** 2422 | * WasmGC-backend specific implementation of conversion code. 2423 | * 2424 | * In the WasmGC backend, all Java values are originally Wasm values, with 2425 | * objects being references to Wasm structs. How those values are represented 2426 | * in Java is governed by the "WebAssembly JavaScript Interface". 2427 | * Java Objects are represented as opaque JS objects, these objects do not have 2428 | * any properties of their own nor are they extensible (though identity with 2429 | * regards to the === operator is preserved), only when passing them back to 2430 | * Wasm code can they be manipulated. 2431 | * Java long values are represented as JS BigInt values. 2432 | * 2433 | * Unlike the JS backend, Java code compiled to WasmGC cannot directly be passed 2434 | * JS objects as arguments due to Wasm's type safety. Instead, JS values are 2435 | * first wrapped in WasmExtern, a custom Java class with some special handling 2436 | * to have it store and externref value in one of its fields. 2437 | * 2438 | * To support JSValue instances, there are Java factory methods for each 2439 | * subclass exported under convert.create.*, which create a new instance of the 2440 | * type and associate it with the given JavaScript value. 2441 | * Instead of having the JS code store the JS native value directly in the 2442 | * WasmGC object (which won't work because they are immutable on the JS side), 2443 | * the JS value is first wrapped in a WasmExtern, and then passed to Java, where 2444 | * that WasmExtern value is stored in a hidden field of the JSValue instance. 2445 | */ 2446 | class WasmGCConversion extends Conversion { 2447 | constructor() { 2448 | super(); 2449 | this.proxyHandlers = new WeakMap(); 2450 | } 2451 | 2452 | #wrapExtern(jsObj) { 2453 | return getExport("extern.wrap")(jsObj); 2454 | } 2455 | 2456 | #unwrapExtern(javaObj) { 2457 | return getExport("extern.unwrap")(javaObj); 2458 | } 2459 | 2460 | handleJSError(jsError) { 2461 | if (jsError instanceof WebAssembly.Exception) { 2462 | // Wasm exceptions can be rethrown as-is. They will be caught in Wasm code 2463 | throw jsError; 2464 | } else { 2465 | // Have Java code wrap the JS error in a Java JSError instance and throw it. 2466 | getExport("convert.throwjserror")(this.javaScriptToJava(jsError)); 2467 | } 2468 | } 2469 | 2470 | extractJavaScriptNumber(jldouble) { 2471 | return getExport("unbox.double")(jldouble); 2472 | } 2473 | 2474 | extractJavaScriptString(jlstring) { 2475 | return charArrayToString(proxyCharArray(getExport("string.tochars")(jlstring))); 2476 | } 2477 | 2478 | extractJavaScriptArray(jarray) { 2479 | const length = getExport("array.length")(jarray); 2480 | const jsarray = new Array(length); 2481 | for (let i = 0; i < length; i++) { 2482 | jsarray[i] = this.javaToJavaScript(getExport("array.object.read")(jarray, i)); 2483 | } 2484 | return jsarray; 2485 | } 2486 | 2487 | createJavaBoolean(x) { 2488 | return getExport("box.boolean")(x); 2489 | } 2490 | 2491 | createJavaByte(x) { 2492 | return getExport("box.byte")(x); 2493 | } 2494 | 2495 | createJavaShort(x) { 2496 | return getExport("box.short")(x); 2497 | } 2498 | 2499 | createJavaCharacter(x) { 2500 | return getExport("box.char")(x); 2501 | } 2502 | 2503 | createJavaInteger(x) { 2504 | return getExport("box.int")(x); 2505 | } 2506 | 2507 | createJavaFloat(x) { 2508 | return getExport("box.float")(x); 2509 | } 2510 | 2511 | createJavaLong(x) { 2512 | return getExport("box.long")(x); 2513 | } 2514 | 2515 | createJavaDouble(x) { 2516 | return getExport("box.double")(x); 2517 | } 2518 | 2519 | getHubKindOrdinal(hub) { 2520 | return getExport("class.getkindordinal")(hub); 2521 | } 2522 | 2523 | getBoxedHub(jlClass) { 2524 | return getExport("class.getboxedhub")(jlClass); 2525 | } 2526 | 2527 | unboxBoolean(jlBoolean) { 2528 | return getExport("unbox.boolean")(jlBoolean); 2529 | } 2530 | 2531 | unboxByte(jlByte) { 2532 | return getExport("unbox.byte")(jlByte); 2533 | } 2534 | 2535 | unboxShort(jlShort) { 2536 | return getExport("unbox.short")(jlShort); 2537 | } 2538 | 2539 | unboxChar(jlChar) { 2540 | return getExport("unbox.char")(jlChar); 2541 | } 2542 | 2543 | unboxInt(jlInt) { 2544 | return getExport("unbox.int")(jlInt); 2545 | } 2546 | 2547 | unboxFloat(jlFloat) { 2548 | return getExport("unbox.float")(jlFloat); 2549 | } 2550 | 2551 | unboxLong(jlLong) { 2552 | return getExport("unbox.long")(jlLong); 2553 | } 2554 | 2555 | unboxDouble(jlDouble) { 2556 | return getExport("unbox.double")(jlDouble); 2557 | } 2558 | 2559 | createJSUndefined() { 2560 | return getExport("convert.create.jsundefined")(); 2561 | } 2562 | 2563 | createJSBoolean(boolean) { 2564 | return getExport("convert.create.jsboolean")(this.#wrapExtern(boolean)); 2565 | } 2566 | 2567 | createJSNumber(number) { 2568 | return getExport("convert.create.jsnumber")(this.#wrapExtern(number)); 2569 | } 2570 | 2571 | createJSBigInt(bigint) { 2572 | return getExport("convert.create.jsbigint")(this.#wrapExtern(bigint)); 2573 | } 2574 | 2575 | createJSString(string) { 2576 | return getExport("convert.create.jsstring")(this.#wrapExtern(string)); 2577 | } 2578 | 2579 | createJSSymbol(symbol) { 2580 | return getExport("convert.create.jssymbol")(this.#wrapExtern(symbol)); 2581 | } 2582 | 2583 | createJSObject(obj) { 2584 | return getExport("convert.create.jsobject")(this.#wrapExtern(obj)); 2585 | } 2586 | 2587 | isInternalJavaObject(obj) { 2588 | return getExport("extern.isjavaobject")(obj); 2589 | } 2590 | 2591 | isPrimitiveHub(hub) { 2592 | return getExport("class.isprimitive")(hub); 2593 | } 2594 | 2595 | isJavaLangString(obj) { 2596 | return getExport("convert.isjavalangstring")(obj); 2597 | } 2598 | 2599 | isJavaLangClass(obj) { 2600 | return getExport("convert.isjavalangclass")(obj); 2601 | } 2602 | 2603 | isInstance(obj, hub) { 2604 | return getExport("object.isinstance")(obj, hub); 2605 | } 2606 | 2607 | getOrCreateProxyHandler(clazz) { 2608 | if (!this.proxyHandlers.has(clazz)) { 2609 | this.proxyHandlers.set(clazz, new WasmGCProxyHandler(clazz)); 2610 | } 2611 | return this.proxyHandlers.get(clazz); 2612 | } 2613 | 2614 | _getProxyHandlerArg(obj) { 2615 | return getExport("object.getclass")(obj); 2616 | } 2617 | 2618 | javaToJavaScript(x) { 2619 | let effectiveJavaObject = x; 2620 | 2621 | /* 2622 | * When catching exceptions in JavaScript, exceptions thrown from Java 2623 | * aren't caught as Java objects, but as WebAssembly.Exception objects. 2624 | * Instead of having to do special handling whenever we catch an 2625 | * exception in JS, converting to JavaScript first unwraps the original 2626 | * Java Throwable before converting. 2627 | */ 2628 | if (x instanceof WebAssembly.Exception && x.is(getExport("tag.throwable"))) { 2629 | effectiveJavaObject = x.getArg(getExport("tag.throwable"), 0); 2630 | } 2631 | 2632 | return this.#unwrapExtern(getExport("convert.javatojavascript")(effectiveJavaObject)); 2633 | } 2634 | 2635 | throwClassCastExceptionImpl(javaObject, tpeNameJavaString) { 2636 | getExport("convert.throwClassCastException")(javaObject, tpeNameJavaString); 2637 | } 2638 | 2639 | coerceJavaProxyToJavaScriptType(proxyHandler, proxy, tpe) { 2640 | const o = proxy[runtime.symbol.javaNative]; 2641 | switch (tpe) { 2642 | case "boolean": 2643 | // Due to Java booleans being numbers, the double-negation is necessary. 2644 | return !!this.#unwrapExtern(getExport("convert.coerce.boolean")(o)); 2645 | case "number": 2646 | return this.#unwrapExtern(getExport("convert.coerce.number")(o)); 2647 | case "bigint": 2648 | const bs = this.#unwrapExtern(getExport("convert.coerce.bigint")(o)); 2649 | return BigInt(bs); 2650 | case "string": 2651 | return this.#unwrapExtern(getExport("convert.coerce.string")(o)); 2652 | case "object": 2653 | return this.#unwrapExtern(getExport("convert.coerce.object")(o)); 2654 | case "function": 2655 | const sam = proxyHandler._getSingleAbstractMethod(proxy); 2656 | if (sam !== undefined) { 2657 | return (...args) => proxyHandler._applyWithObject(proxy, args); 2658 | } 2659 | this.throwClassCastException(o, tpe); 2660 | case Uint8Array: 2661 | case Int8Array: 2662 | case Uint16Array: 2663 | case Int16Array: 2664 | case Int32Array: 2665 | case Float32Array: 2666 | case BigInt64Array: 2667 | case Float64Array: 2668 | // TODO GR-60603 Support array coercion 2669 | throw new Error("Coercion to arrays is not supported yet"); 2670 | default: 2671 | this.throwClassCastException(o, tpe); 2672 | } 2673 | } 2674 | } 2675 | 2676 | const METADATA_PREFIX = "META."; 2677 | const SAM_PREFIX = "SAM."; 2678 | const METADATA_SEPARATOR = " "; 2679 | 2680 | class WasmGCProxyHandler extends ProxyHandler { 2681 | #classMetadata = null; 2682 | 2683 | constructor(clazz) { 2684 | super(); 2685 | this.clazz = clazz; 2686 | } 2687 | 2688 | #lookupClass(name) { 2689 | const clazz = getExport("conversion.classfromencoding")(toJavaString(name)); 2690 | if (!clazz) { 2691 | throw new Error("Failed to lookup class " + name); 2692 | } 2693 | 2694 | return clazz; 2695 | } 2696 | 2697 | _getClassMetadata() { 2698 | if (!this.#classMetadata) { 2699 | this.#classMetadata = new ClassMetadata({}, this.#extractSingleAbstractMethod(), this.#createMethodTable()); 2700 | } 2701 | return this.#classMetadata; 2702 | } 2703 | 2704 | #decodeMetadata(exports, name, prefix) { 2705 | if (name.startsWith(prefix)) { 2706 | const parts = name.slice(prefix.length).split(METADATA_SEPARATOR); 2707 | if (parts.length < 3) { 2708 | throw new Error("Malformed metadata: " + name); 2709 | } 2710 | const classId = parts[0]; 2711 | 2712 | if (this.#lookupClass(classId) == this.clazz) { 2713 | const methodName = parts[1]; 2714 | const returnTypeId = parts[2]; 2715 | const argTypeIds = parts.slice(3); 2716 | 2717 | return [ 2718 | methodName, 2719 | mmeta( 2720 | exports[name], 2721 | this.#lookupClass(returnTypeId), 2722 | ...argTypeIds.map((i) => this.#lookupClass(i)) 2723 | ), 2724 | ]; 2725 | } 2726 | } 2727 | 2728 | return undefined; 2729 | } 2730 | 2731 | #extractSingleAbstractMethod() { 2732 | const exports = getExports(); 2733 | 2734 | for (const name in exports) { 2735 | const meta = this.#decodeMetadata(exports, name, SAM_PREFIX); 2736 | if (meta !== undefined) { 2737 | return meta[1]; 2738 | } 2739 | } 2740 | 2741 | return undefined; 2742 | } 2743 | 2744 | #createMethodTable() { 2745 | const exports = getExports(); 2746 | const methodTable = {}; 2747 | 2748 | for (const name in exports) { 2749 | const meta = this.#decodeMetadata(exports, name, METADATA_PREFIX); 2750 | if (meta !== undefined) { 2751 | let methodName = meta[0]; 2752 | 2753 | if (methodName === "") { 2754 | methodName = runtime.symbol.ctor; 2755 | } 2756 | 2757 | if (!methodTable.hasOwnProperty(methodName)) { 2758 | methodTable[methodName] = []; 2759 | } 2760 | 2761 | methodTable[methodName].push(meta[1]); 2762 | } 2763 | } 2764 | 2765 | return methodTable; 2766 | } 2767 | 2768 | _getClassName() { 2769 | return conversion.extractJavaScriptString(getExport("class.getname")(this.clazz)); 2770 | } 2771 | 2772 | _linkMethodPrototype() { 2773 | // Link the prototype chain of the superclass' proxy handler, to include super methods. 2774 | if (!getExport("class.isjavalangobject")(this.clazz)) { 2775 | const parentClass = getExport("class.superclass")(this.clazz); 2776 | const parentProxyHandler = conversion.getOrCreateProxyHandler(parentClass); 2777 | Object.setPrototypeOf(this._getMethods(), parentProxyHandler._getMethods()); 2778 | } 2779 | } 2780 | 2781 | _createInstance(hub) { 2782 | return getExport("unsafe.create")(hub); 2783 | } 2784 | } 2785 | 2786 | const conversion = new WasmGCConversion(); 2787 | const createVM = function(vmArgs, data) { 2788 | runtime.data = data; 2789 | wasmRun(vmArgs); 2790 | 2791 | return vm; 2792 | 2793 | }; 2794 | GraalVM.Config = Config; 2795 | /** @suppress {checkVars,duplicate} */ GraalVM.run = async function (vmArgs, config = new GraalVM.Config()) { 2796 | let data = new Data(config); 2797 | for (let libname in config.libraries) { 2798 | const content = await runtime.fetchText(config.libraries[libname]); 2799 | data.libraries[libname] = content; 2800 | } 2801 | data.wasm = await wasmInstantiate(config, vmArgs); 2802 | let vm = createVM(vmArgs, data); 2803 | return vm; 2804 | } 2805 | })(); 2806 | 2807 | })(); 2808 | 2809 | })(); 2810 | 2811 | (function() { 2812 | /* 2813 | * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. 2814 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2815 | * 2816 | * This code is free software; you can redistribute it and/or modify it 2817 | * under the terms of the GNU General Public License version 2 only, as 2818 | * published by the Free Software Foundation. Oracle designates this 2819 | * particular file as subject to the "Classpath" exception as provided 2820 | * by Oracle in the LICENSE file that accompanied this code. 2821 | * 2822 | * This code is distributed in the hope that it will be useful, but WITHOUT 2823 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 2824 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 2825 | * version 2 for more details (a copy is included in the LICENSE file that 2826 | * accompanied this code). 2827 | * 2828 | * You should have received a copy of the GNU General Public License version 2829 | * 2 along with this work; if not, write to the Free Software Foundation, 2830 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2831 | * 2832 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2833 | * or visit www.oracle.com if you need additional information or have any 2834 | * questions. 2835 | */ 2836 | 2837 | /** 2838 | * Try to load commandline arguments for various JS runtimes. 2839 | */ 2840 | function load_cmd_args() { 2841 | if (typeof process === "object" && "argv" in process) { 2842 | // nodejs 2843 | return process.argv.slice(2); 2844 | } else if (typeof scriptArgs == "object") { 2845 | // spidermonkey 2846 | return scriptArgs; 2847 | } 2848 | 2849 | return []; 2850 | } 2851 | 2852 | const config = new GraalVM.Config(); 2853 | GraalVM.run(load_cmd_args(),config).catch(console.error); 2854 | })(); -------------------------------------------------------------------------------- /core.js.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/graal-clojure-wasm/c2118c523b8d22e19f9e5fe9c5b3e9d1ad82c52b/core.js.wasm -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.graalvm.polyglot/polyglot {:mvn/version "24.2.1"} 2 | org.graalvm/webimage {:local/root "$JAVA_HOME/lib/svm/tools/svm-wasm/builder/svm-wasm-api.jar"} 3 | clj.native-image/clj.native-image {:git/url "https://github.com/taylorwood/clj.native-image.git" 4 | :sha "4604ae76855e09cdabc0a2ecc5a7de2cc5b775d6"}} 5 | :paths ["src" "target/classes"] 6 | :aliases {:classes {:main-opts ["-m" "build"]} 7 | :native-image {:main-opts ["-m" "clj.native-image" "core" 8 | "--initialize-at-build-time" "--tool:svm-wasm" "--no-fallback" 9 | "--emit" "build-report" "-Os" 10 | "-H:IncludeLocales=en" "-H:IncludeResources='.*\\.properties$'" 11 | ;; optional native image name override 12 | "-H:Name=core" 13 | ;; turns unrelated erros into warnings 14 | "-H:+AllowDeprecatedBuilderClassesOnImageClasspath" 15 | "-march=native"] 16 | :jvm-opts ["-Dclojure.compiler.direct-linking=true" 17 | "-Dclojure.spec.skip-macros=true"]}}} 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/browser/Browser.java: -------------------------------------------------------------------------------- 1 | package browser; 2 | 3 | import org.graalvm.webimage.api.JS; 4 | import org.graalvm.webimage.api.JSObject; 5 | 6 | public class Browser { 7 | public static void main() { 8 | try { 9 | // TODO GR-62854 Here to ensure handleEvent and run is generated. Remove once 10 | // objects passed to @JS methods automatically have their SAM registered. 11 | sink(Callback.class.getDeclaredMethod("run", JSObject.class)); 12 | } catch (NoSuchMethodException e) { 13 | throw new RuntimeException(e); 14 | } 15 | } 16 | 17 | @JS("") 18 | private static native void sink(Object o); 19 | 20 | @JS.Coerce 21 | @JS("return globalThis;") 22 | public static native JSObject globalThis(); 23 | 24 | @JS.Coerce 25 | @JS("return document.querySelector(selector);") 26 | public static native JSObject querySelector(String selector); 27 | 28 | @JS.Coerce 29 | @JS("return document.createElement(tag);") 30 | public static native JSObject createElement(String tag); 31 | 32 | @JS.Coerce 33 | @JS("return parent.appendChild(child);") 34 | public static native void appendChild(JSObject parent, JSObject child); 35 | 36 | @JS.Coerce 37 | @JS("element.addEventListener(eventType, handler);") 38 | public static native void addEventListener(JSObject element, String eventType, Callback handler); 39 | } -------------------------------------------------------------------------------- /src/browser/Callback.java: -------------------------------------------------------------------------------- 1 | package browser; 2 | 3 | import org.graalvm.webimage.api.JSObject; 4 | 5 | @FunctionalInterface 6 | public interface Callback { 7 | void run(JSObject event); 8 | } -------------------------------------------------------------------------------- /src/build.clj: -------------------------------------------------------------------------------- 1 | (ns build) 2 | -------------------------------------------------------------------------------- /src/core.clj: -------------------------------------------------------------------------------- 1 | (ns core 2 | (:import [browser Browser Callback] 3 | [org.graalvm.webimage.api JSObject]) 4 | (:gen-class)) 5 | 6 | ;; Interop, DOM 7 | 8 | (defn as-callback [f] 9 | (reify Callback 10 | (run [this value] 11 | (f value)))) 12 | 13 | (defn invoke-method [^JSObject object ^String method & args] 14 | (.call ^JSObject (.get object method) object (object-array args))) 15 | 16 | (defn render-dom [] 17 | (Browser/main) 18 | (let [window (Browser/globalThis) 19 | root (Browser/querySelector "#btn-root") 20 | button (Browser/createElement "button") 21 | on-click (fn [event] 22 | (invoke-method window "alert" "Hello, from Clojure in WASM!"))] 23 | (.set button "textContent" "Press me") 24 | (Browser/addEventListener button "click" (as-callback on-click)) 25 | (Browser/appendChild root button))) 26 | 27 | ;; Benchmark 28 | 29 | (def nanosecond 1) 30 | (def microsecond (* nanosecond 1000)) 31 | (def millisecond (* microsecond 1000)) 32 | (def second_ (* millisecond 1000)) 33 | 34 | (defn format-duration 35 | "format-duration turns nanosecond durations, such as those provided 36 | by bench and converts them to a microsecond, millisecond, or second 37 | value (ie. 32ns, 43.2µs, 54.7ms, 3.1s)." 38 | [ns] 39 | (cond 40 | (>= ns second_),,,, (format (str "%.1fs") (float (/ ns second_))) 41 | (>= ns millisecond) (format (str "%.1fms") (float (/ ns millisecond))) 42 | (>= ns microsecond) (format (str "%.1fµs") (float (/ ns microsecond))) 43 | :else,,,,,,,,,,,,,, (format (str "%dns") ns))) 44 | 45 | (defn ^:private format-order-of-magnitude [ops] 46 | (as-> (str ops) $ 47 | (filter #{\0} $) 48 | (count $) 49 | (format "10**%d" $))) 50 | 51 | (def max-ops 10000000) 52 | (def max-duration second_) 53 | 54 | (defmacro ^:private time-ns [ex] 55 | `(let [start# (. System (nanoTime))] 56 | (do ~ex (- (. System (nanoTime)) start#)))) 57 | 58 | (defn time-it [f n] 59 | (time-ns (dotimes [_ n] (f)))) 60 | 61 | (defn bench 62 | "bench receives a function `f` and an integer `n`. 63 | It runs `f` `n` times and reports the total elapsed 64 | time as well as the per-operation average time elapsed. 65 | It is intended to be called by a higher-level function 66 | that will pass in successively higher values of `n` 67 | until a time threshold is crossed." 68 | [f n] 69 | (let [elapsed (time-it f n) 70 | average (quot elapsed n)] 71 | {:total-ops n 72 | :total-ns elapsed 73 | :per-op-ns average})) 74 | 75 | (defn- within-time-threshold [{:keys [total-ns]}] (< total-ns max-duration)) 76 | (defn- within-ops-threshold [{:keys [total-ops]}] (< total-ops max-ops)) 77 | 78 | (def ^:private within-thresholds 79 | (every-pred within-time-threshold 80 | within-ops-threshold)) 81 | 82 | (defn benchmark 83 | "benchmark passes the provided function `f` to bench 84 | with successively higher `n` values until it has either: 85 | 1. exhausted a reasonable amount of time or 86 | 2. measured enough executions to have an accurate 87 | per-operation average. 88 | It returns the low-level results of the last call to bench." 89 | [f] 90 | (let [by-powers-of-10 #(* 10 %) 91 | benchmark #(bench f %)] 92 | (as-> (iterate by-powers-of-10 1) $ 93 | (map benchmark $) 94 | (drop-while within-thresholds $) 95 | (first $)))) 96 | 97 | (defn report 98 | "Translates the result of benchmark into more human-readable values. 99 | Can be passed to println for a decent report." 100 | [f] 101 | (let [{:keys [total-ops total-ns per-op-ns]} (benchmark f)] 102 | {:total-ops (format-order-of-magnitude total-ops) 103 | :total-time (format-duration total-ns) 104 | :per-op-time (format-duration per-op-ns)})) 105 | 106 | (defn run-benchmark [] 107 | (println "- big reduce: " (report #(reduce * (range 10000000)))) 108 | (println "- int/float division:" (report #(int (/ 101 10)))) 109 | (println "- float division: " (report #(/ 101 10))) 110 | (println "- integer division: " (report #(quot 101 10)))) 111 | 112 | (defn -main [& args] 113 | (println "Hello, World!") 114 | (render-dom) 115 | (.set (Browser/globalThis) "runBenchmark" (as-callback (fn [_] (run-benchmark))))) --------------------------------------------------------------------------------