aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--bb.edn31
-rw-r--r--cljcc-compiler/build.clj28
-rw-r--r--cljcc-compiler/deps.edn8
-rw-r--r--cljcc-compiler/src/cljcc/cljcc.clj25
-rw-r--r--frontend/index.html19
-rw-r--r--frontend/script.js52
-rw-r--r--public/index.html268
-rw-r--r--public/js/cljcc-lib-wasm.js2814
9 files changed, 3244 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 5d70931..9b26a19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,4 @@
/classes
/target
.aider*
+public/js/cljcc-lib-wasm.js.wasm
diff --git a/bb.edn b/bb.edn
index 54f6027..00c6add 100644
--- a/bb.edn
+++ b/bb.edn
@@ -16,6 +16,23 @@
storm {:doc "Starts a nrepl session with storm debugger."
:task (apply clojure "-M:storm" *command-line-args*)}
+ lib:run:main {:doc "Run's main only for cljcc library."
+ :task (apply shell {:dir "cljcc-compiler"} "clojure -M -m cljcc.cljcc" *command-line-args*)}
+
+ lib:build:jar {:doc "Builds uberjar only for cljcc lib."
+ :task (shell {:dir "cljcc-compiler"} "clojure -T:build ci")}
+
+ lib:build:wasm {:doc "Builds native WASM image."
+ :depends [lib:build:jar]
+ :task (shell {:dir "target/lib"}
+ "native-image"
+ "--tool:svm-wasm"
+ "-jar" "cljcc-lib.jar"
+ "-o" "cljcc-lib-wasm"
+ "--features=clj_easy.graal_build_time.InitClojureClasses"
+ "--initialize-at-build-time"
+ "--verbose")}
+
cli:run:main {:doc "Run's main CLI function."
:task (apply shell {:dir "cli"} "clojure -M -m cli.cli" *command-line-args*)}
@@ -26,6 +43,18 @@
:depends [cli:build:jar]
:task (apply shell "java -jar target/cli/cljcc-cli.jar" *command-line-args*)}
+ cli:build:wasm {:doc "Builds native image for WASM."
+ :depends [cli:build:jar]
+ :task
+ (shell {:dir "target/cli"}
+ "native-image"
+ "--tool:svm-wasm"
+ "-jar" "cljcc-cli.jar"
+ "-o" "cljcc-cli-wasm"
+ "--features=clj_easy.graal_build_time.InitClojureClasses"
+ "--initialize-at-build-time"
+ "--verbose")}
+
cli:build:native {:doc "Builds native image for CLI."
:depends [cli:build:jar]
:task
@@ -33,7 +62,7 @@
"native-image"
"-jar" "cljcc-cli.jar"
"-o" "cljcc-cli"
- "-Ob" ; quick compilation flag for development
+ "-Ob" ; quick compilation flag for development
"-H:+ReportExceptionStackTraces"
"--features=clj_easy.graal_build_time.InitClojureClasses"
"--initialize-at-build-time"
diff --git a/cljcc-compiler/build.clj b/cljcc-compiler/build.clj
new file mode 100644
index 0000000..68d7f95
--- /dev/null
+++ b/cljcc-compiler/build.clj
@@ -0,0 +1,28 @@
+(ns build
+ (:refer-clojure :exclude [test])
+ (:require [clojure.tools.build.api :as b]))
+
+(def lib 'net.clojars.cljcc-lib/cljcc-lib)
+(def main 'cljcc.cljcc)
+(def class-dir "../target/classes")
+
+(defn- uber-opts [opts]
+ (assoc opts
+ :lib lib :main main
+ :uber-file "../target/lib/cljcc-lib.jar"
+ :basis (b/create-basis {})
+ :class-dir class-dir
+ :src-dirs ["src"]
+ :ns-compile [main]))
+
+(defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts]
+ (b/delete {:path "../target"})
+ (let [opts (uber-opts opts)]
+ (println "\nCopying source...")
+ (b/copy-dir {:src-dirs ["resources" "src"] :target-dir class-dir})
+ (println (str "\nCompiling " main "..."))
+ (b/compile-clj opts)
+ (println "\nBuilding JAR...")
+ (b/uber opts)
+ (println "\nJAR built."))
+ opts)
diff --git a/cljcc-compiler/deps.edn b/cljcc-compiler/deps.edn
index 52fd6d7..2d5cfc3 100644
--- a/cljcc-compiler/deps.edn
+++ b/cljcc-compiler/deps.edn
@@ -1,4 +1,10 @@
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.match {:mvn/version "1.1.0"}
- metosin/malli {:mvn/version "0.16.4"}}}
+ com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}
+ metosin/malli {:mvn/version "0.16.4"}}
+ :aliases {:run {:main-opts ["-m" "cljcc.cljcc"]}
+ :build {:deps {io.github.clojure/tools.build
+ {:mvn/version "0.10.3"}}
+ :jvm-opts ["-Dclojure.compiler.direct-linking=true"]
+ :ns-default build}}}
diff --git a/cljcc-compiler/src/cljcc/cljcc.clj b/cljcc-compiler/src/cljcc/cljcc.clj
index fc088fc..e5c1e69 100644
--- a/cljcc-compiler/src/cljcc/cljcc.clj
+++ b/cljcc-compiler/src/cljcc/cljcc.clj
@@ -9,6 +9,8 @@
[cljcc.emit :as emit])
(:gen-class))
+(set! *warn-on-reflection* true)
+
(def valid-os-targets #{:mac :linux})
(def valid-stages #{:lex :parse :validate :tacky :codegen :emit})
@@ -47,3 +49,26 @@
stages-to-run (vec (take stage-idx stages))]
(reduce (fn [acc f] (f acc)) source stages-to-run)))
+(defn- validate-os [os]
+ (let [valid-os #{"mac" "linux"}]
+ (assert (valid-os os) (str "OS: " os "is not valid." "OS type is not valid. Valid os: " (vec valid-os))))
+ os)
+
+(defn- validate-stage [stage]
+ (let [valid-stages #{"lex" "parse" "validate" "tacky" "codegen" "emit"}]
+ (assert (valid-stages stage) (str "Stage is not valid. Valid stages: " (vec valid-stages)))
+ stage))
+
+(defn -main [& args]
+ (let [[source os stage] args
+ source (or source "")
+ os (or os "")
+ stage (or stage "")]
+ (validate-os os)
+ (validate-stage stage)
+ (println (run source {:options {:os (keyword os)
+ :stage (keyword stage)}}))))
+
+(comment
+
+ (-main "int main(void) {return 42;}" "linux" "lex"))
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..7499d08
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="no-js" lang="">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>GraalVM test</title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
+
+ </head>
+ <body>
+
+
+ <div id="output"></div>
+ <script src="script.js"></script>
+ </body>
+</html>
diff --git a/frontend/script.js b/frontend/script.js
new file mode 100644
index 0000000..6e95792
--- /dev/null
+++ b/frontend/script.js
@@ -0,0 +1,52 @@
+// Assuming your WASM file is named "mymodule.wasm"
+async function loadWasmModule() {
+ // Fetch the WASM file
+ const wasmResponse = await fetch('../target/lib/cljcc-lib-wasm.js.wasm');
+ const wasmBuffer = await wasmResponse.arrayBuffer();
+
+ // Instantiate the WASM module
+ const wasmResult = await WebAssembly.instantiate(wasmBuffer, {
+ env: {
+ // Any imported functions needed by your WASM module
+ // For example, if your Java code uses console logging:
+ printString: function(ptr, len) {
+ // Implementation to handle string output
+ const bytes = new Uint8Array(memory.buffer, ptr, len);
+ const string = new TextDecoder('utf8').decode(bytes);
+ console.log(string);
+ document.getElementById('output').innerText += string + '\n';
+ }
+ }
+ });
+
+ // Store the instance and memory
+ const instance = wasmResult.instance;
+ const memory = instance.exports.memory;
+
+ // Call the exported function that returns a string
+ // For example, if your Java code exports a function called "getString"
+ const stringPointer = instance.exports.getString();
+
+ // Now we need to read the string from memory
+ // This depends on how your GraalVM WASM image handles strings
+ // Typically you would:
+ // 1. Get the length of the string (via another export or a convention)
+ const stringLength = instance.exports.getStringLength(); // If this is exported
+
+ // 2. Convert the pointer and length to a JavaScript string
+ const bytes = new Uint8Array(memory.buffer, stringPointer, stringLength);
+ const result = new TextDecoder('utf8').decode(bytes);
+
+ // 3. Display the result
+ document.getElementById('output').innerText = result;
+
+ return instance;
+}
+
+// Load the WASM module when the page loads
+window.onload = function() {
+ loadWasmModule().catch(err => {
+ console.error("Failed to load WASM module:", err);
+ document.getElementById('output').innerText = "Error: " + err.message;
+ });
+};
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..c0820b2
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,268 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>cljcc: C compiler in Clojure</title>
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
+ <script type="module" src="js/cljcc-lib-wasm.js"></script>
+</head>
+
+<body class="bg-gray-50 text-gray-800 font-sans">
+ <div class="max-w-6xl mx-auto px-4 py-8">
+ <header class="mb-6">
+ <h1 class="text-2xl font-semibold">cljcc </h1>
+ <p class="text-gray-600 text-sm">C compiler implemented in Clojure.</p>
+ </header>
+
+ <div class="mb-4">
+ <p class="text-gray-700">C compiler implemented in Clojure. Based on <a href="https://nostarch.com/writing-c-compiler" class="underline text-blue-600 hover:text-blue-800 visited:text-purple-600">Writing a C Compiler by Nora Sandler</a> </p>
+ <p class="text-gray-700">
+ Clojure codebase is compiled to WASM using <a href="https://graalvm.github.io/graalvm-demos/native-image/wasm-javac/" class="underline text-blue-600 hover:text-blue-800 visited:text-purple-600">GraalVM native image.</a>
+ </p>
+
+ </div>
+
+ <div class="mb-4">
+ <p class="text-gray-700">
+ I have only implemented the first 12 chapters of the book. Refer the supported syntax section below.
+ </p>
+
+ <p class="text-gray-700">
+ Wrote about how I approached implementing this in Clojure. <a class = "underline text-blue-600 hover:text-blue-800 visited:text-purple-600" href="agrawal.me/posts/writing-a-c-compiler-in-clojure/">Writing a C Compiler in Clojure</a>
+ </p>
+ </div>
+
+ <div class="mb-4">
+ <p class="text-gray-700">
+ <a href="https://github.com/kaepr/cljcc" class="underline text-blue-600 hover:text-blue-800 visited:text-purple-600">Github Source: kaepr/cljcc</a>
+ </p>
+
+ </div>
+
+ <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 mb-4">
+ <div class="w-full md:w-1/2">
+ <label for="input" class="block text-sm font-medium mb-1">Input (C code)</label>
+ <textarea id="input" class="w-full h-80 p-3 border border-gray-300 rounded font-mono text-sm" placeholder="Enter your C code here..."></textarea>
+ </div>
+
+ <div class="w-full md:w-1/2">
+ <label for="output" class="block text-sm font-medium mb-1">Output</label>
+ <textarea id="output" class="w-full h-80 p-3 border border-gray-300 bg-gray-50 rounded font-mono text-sm" placeholder="Compiler output will appear here..." readonly></textarea>
+ </div>
+ </div>
+
+ <div class="mb-6">
+ <p class="text-sm font-medium text-gray-700 mb-2">Compilation Stage:</p>
+ <div class="flex flex-wrap gap-4">
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="lex" class="form-radio text-gray-800">
+ <span class="ml-2 text-sm">Tokenize</span>
+ </label>
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="parse" class="form-radio text-gray-800">
+ <span class="ml-2 text-sm">Parse</span>
+ </label>
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="validate" class="form-radio text-gray-800">
+ <span class="ml-2 text-sm">Type Check</span>
+ </label>
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="tacky" class="form-radio text-gray-800">
+ <span class="ml-2 text-sm">Tacky IR</span>
+ </label>
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="codegen" class="form-radio text-gray-800">
+ <span class="ml-2 text-sm">Codegen</span>
+ </label>
+ <label class="inline-flex items-center">
+ <input type="radio" name="compilationStage" value="emit" class="form-radio text-gray-800" checked>
+ <span class="ml-2 text-sm">Generate Assembly</span>
+ </label>
+ </div>
+ </div>
+
+ <div class="mb-6">
+ <button onclick="runSelectedCompilerStage()" class="px-4 py-2 bg-blue-700 text-white rounded hover:bg-blue-600 transition-colors text-sm font-medium">Compile</button>
+ </div>
+
+
+ <!-- Supported Syntax Section -->
+ <div class="mt-12 pt-6 border-t border-gray-200">
+ <h2 class="text-xl font-semibold mb-4">Supported Syntax</h2>
+
+ <div class="space-y-6">
+ <div>
+ <h3 class="text-md font-medium mb-2">Basic Features</h3>
+ <pre class="bg-gray-100 p-3 rounded border border-gray-300 text-sm font-mono overflow-auto">
+<code>// Types
+int, long
+
+// Variables
+int x; // Local variables
+static long count = 0; // Static variables
+extern long count2 = 0; // Extern variables
+
+// Control Flow
+if (x > 0) { ... } else { ... }
+for (int i = 0; i < n; i++) { ... }
+while (condition) { ... }
+do { ... } while (condition);
+
+// Functions
+int main(void);
+int add(int a, int b) { return a + b; }
+
+// Operators
++, -, *, /, %, =, ==, !=, >, <, >=, <=, &&, ||, !</code></pre>
+ </div>
+
+ <div>
+ <h3 class="text-md font-medium mb-2">Example Code</h3>
+
+ <pre class="mb-2 bg-gray-100 p-3 rounded border border-gray-300 text-sm font-mono overflow-auto">
+<code>int main(void) {
+ return 42;
+}
+</code></pre>
+
+ <pre class="mb-2 bg-gray-100 p-3 rounded border border-gray-300 text-sm font-mono overflow-auto">
+<code>int main(void) {
+ static int x = 1;
+ if (x == 42)
+ return x;
+ x = x + 1;
+ return main();
+}</code></pre>
+
+
+ <pre class="mb-2 bg-gray-100 p-3 rounded border border-gray-300 text-sm font-mono overflow-auto">
+<code>static int x = 20;
+
+int main(void) {
+ int y = 22;
+ return x + y;
+}</code></pre>
+ </div>
+
+ </div>
+
+ <script>
+ document.addEventListener('DOMContentLoaded', function() {
+ // You can call initialization functions from your compiler.js here
+ if (typeof initCompiler === 'function') {
+ initCompiler();
+ }
+
+ document.getElementById('input').value = 'int main(void) {\n return 0;\n}';
+ });
+
+
+ // Store original console methods
+ var originalConsoleLog = console.log;
+ var originalConsoleError = console.error;
+
+ // Variables to store captured output
+ var capturedLogs = [];
+ var capturedErrors = [];
+
+ // Override console.log
+ console.log = function() {
+ // Capture the arguments
+ var args = Array.from(arguments).map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ );
+ var message = args.join(' ');
+
+ // Add to our captured logs array
+ capturedLogs.push(message);
+
+ // Call original method
+ originalConsoleLog.apply(console, arguments);
+ };
+
+ // Override console.error
+ console.error = function() {
+ // Capture the arguments
+ var args = Array.from(arguments).map(arg =>
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+ );
+ var message = args.join(' ');
+
+ // Add to our captured errors array
+ capturedErrors.push(message);
+
+ // Call original method
+ originalConsoleError.apply(console, arguments);
+ };
+
+ // Function to clear captured logs
+ function clearCapturedOutput() {
+ capturedLogs = [];
+ capturedErrors = [];
+ }
+
+
+ async function runCompiler(source, stage) {
+ // https://gist.github.com/designbyadrian/2eb329c853516cef618a
+ try {
+ const args = [source, "linux", stage];
+ const config = new GraalVM.Config();
+ await GraalVM.run(args,config).catch(console.error);
+ } catch (e) {
+ }
+ }
+
+ function getCompilationStage() {
+ const radioButtons = document.getElementsByName('compilationStage');
+ for (const radioButton of radioButtons) {
+ if (radioButton.checked) {
+ return radioButton.value;
+ }
+ }
+
+ return 'emit';
+ }
+
+ async function runSelectedCompilerStage() {
+ clearCapturedOutput();
+ const stage = getCompilationStage();
+ const source = document.getElementById('input').value;
+ const outputElement = document.getElementById('output');
+
+ try {
+ let result = "";
+ await runCompiler(source, stage);
+ let output = "";
+
+
+ // Add console logs if there are any
+ if (capturedLogs.length > 0) {
+ output += capturedLogs.join("\n");
+ }
+
+ // Add console errors if there are any
+ if (capturedErrors.length > 0) {
+ output += "Errors:\n" + capturedErrors.join("\n");
+ }
+
+ outputElement.value = output.trim();
+ } catch (error) {
+ let errorOutput = "";
+
+ // Add console logs if there are any
+ if (capturedLogs.length > 0) {
+ errorOutput += capturedLogs.join("\n");
+ }
+
+ if (capturedErrors.length > 0) {
+ errorOutput += capturedErrors.join("\n");
+ }
+
+ outputElement.value = errorOutput;
+ }
+ }
+
+ </script>
+</body>
+</html>
diff --git a/public/js/cljcc-lib-wasm.js b/public/js/cljcc-lib-wasm.js
new file mode 100644
index 0000000..cf475fb
--- /dev/null
+++ b/public/js/cljcc-lib-wasm.js
@@ -0,0 +1,2814 @@
+'use strict';
+
+
+/**
+ * @suppress {checkVars,checkTypes,duplicate}
+ * @nocollapse
+*/
+var GraalVM = {};
+window.GraalVM = GraalVM;
+(function() {(function() {(function() {
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Represents a potential feature of the JS runtime.
+ *
+ * During construction, the detection callback is called to detect whether the runtime supports the feature.
+ * The callback returns whether the feature was detected and may store some data
+ * into the 'data' member for future use.
+ */
+class Feature {
+ constructor(descr, detection_callback) {
+ this.description = descr;
+ this.data = {};
+ this.detected = detection_callback(this.data);
+ }
+}
+
+/**
+ * Specialized feature to detect global variables.
+ */
+class GlobalVariableFeature extends Feature {
+ constructor(descr, var_name) {
+ super(descr, GlobalVariableFeature.detection_callback.bind(null, var_name));
+ }
+
+ /**
+ * Function to detect a global variable.
+ *
+ * Uses the 'globalThis' object which should represent the global scope in
+ * modern runtimes.
+ */
+ static detection_callback(var_name, data) {
+ if (var_name in globalThis) {
+ data.global = globalThis[var_name];
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the global detected by this feature.
+ */
+ get() {
+ return this.data.global;
+ }
+}
+
+/**
+ * Specialized feature to detect the presence of Node.js modules.
+ */
+class RequireFeature extends Feature {
+ constructor(descr, module_name) {
+ super(descr, RequireFeature.detection_callback.bind(null, module_name));
+ }
+
+ /**
+ * Function to detect a Node.js module.
+ */
+ static detection_callback(module_name, data) {
+ if (typeof require != "function") {
+ return false;
+ }
+
+ try {
+ data.module = require(module_name);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the module detected by this feature.
+ */
+ get() {
+ return this.data.module;
+ }
+}
+
+/**
+ * Collection of features needed for runtime functions.
+ */
+let features = {
+ // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
+ fetch: new GlobalVariableFeature("Presence of the Fetch API", "fetch"),
+
+ // https://nodejs.org/api/fs.html#promises-api
+ node_fs: new RequireFeature("Presence of Node.js fs promises module", "fs/promises"),
+
+ // https://nodejs.org/api/https.html
+ node_https: new RequireFeature("Presence of Node.js https module", "https"),
+
+ // https://nodejs.org/api/process.html
+ node_process: new RequireFeature("Presence of Node.js process module", "process"),
+
+ /**
+ * Technically, '__filename' is not a global variable, it is a variable in the module scope.
+ * https://nodejs.org/api/globals.html#__filename
+ */
+ filename: new Feature("Presence of __filename global", (d) => {
+ if (typeof __filename != "undefined") {
+ d.filename = __filename;
+ return true;
+ }
+
+ return false;
+ }),
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript
+ currentScript: new Feature("Presence of document.currentScript global", (d) => {
+ if (
+ typeof document != "undefined" &&
+ "currentScript" in document &&
+ document.currentScript != null &&
+ "src" in document.currentScript
+ ) {
+ d.currentScript = document.currentScript;
+ return true;
+ }
+
+ return false;
+ }),
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/location
+ location: new Feature("Presence of Web worker location", (d) => {
+ if (typeof self != "undefined") {
+ d.location = self.location;
+ return true;
+ }
+ return false;
+ }),
+};
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+function charArrayToString(arr) {
+ let res = [];
+
+ const len = 512;
+
+ for (let i = 0; i < arr.length; i += len) {
+ res.push(String.fromCharCode(...arr.slice(i, i + len)));
+ }
+ return res.join("");
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+function llog(p) {
+ if (p instanceof Error) {
+ console.log(p);
+ } else if (p instanceof Object) {
+ console.log(p.toString());
+ } else {
+ console.log(p);
+ }
+}
+
+/**
+ * A writer emulating stdout and stderr using console.log and console.error
+ *
+ * Since those functions cannot print without newline, lines are buffered but
+ * without a max buffer size.
+ */
+class ConsoleWriter {
+ constructor(logger) {
+ this.line = "";
+ this.newline = "\n".charCodeAt(0);
+ this.closed = false;
+ this.logger = logger;
+ }
+
+ printChars(chars) {
+ let index = chars.lastIndexOf(this.newline);
+
+ if (index >= 0) {
+ this.line += charArrayToString(chars.slice(0, index));
+ this.writeLine();
+ chars = chars.slice(index + 1);
+ }
+
+ this.line += charArrayToString(chars);
+ }
+
+ writeLine() {
+ this.logger(this.line);
+ this.line = "";
+ }
+
+ flush() {
+ if (this.line.length > 0) {
+ // In JS we cannot print without newline, so flushing will always produce one
+ this.writeLine();
+ }
+ }
+
+ close() {
+ if (this.closed) {
+ return;
+ }
+ this.closed = true;
+
+ this.flush();
+ }
+}
+
+var stdoutWriter = new ConsoleWriter(console.log);
+var stderrWriter = new ConsoleWriter(console.error);
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Class that holds the configuration of the VM.
+ */
+class Config {
+ constructor() {
+ this.libraries = {};
+ this.currentWorkingDirectory = "/root";
+ }
+}
+
+/**
+ * Class that holds the data required to start the VM.
+ */
+class Data {
+ constructor(config) {
+ /**
+ * User-specified configuration object.
+ *
+ * @type Config
+ */
+ this.config = config;
+
+ /**
+ * Optionally holds a binary support file.
+ */
+ this.binaryImageHeap = null;
+
+ /**
+ * Maps the library names to prefetched content during VM bootup.
+ * After the VM is initialized, the keys are retained, but values are set to null.
+ */
+ this.libraries = {};
+ }
+}
+
+/**
+ * Dummy object that exists only to avoid warnings in IDEs for the `$t[name]` expressions (used in JavaScript support files).
+ */
+const $t = {};
+
+/**
+ * For a given JavaScript class, returns an object that can lookup its properties.
+ */
+function cprops(cls) {
+ return cls.prototype;
+}
+
+/**
+ * Placeholder for lazy-value initializers.
+ */
+class LazyValueThunk {
+ constructor(initializer) {
+ this.initializer = initializer;
+ }
+}
+
+/**
+ * Creates a lazy property on the specified object.
+ */
+function lazy(obj, name, initializer) {
+ let state = new LazyValueThunk(initializer);
+ Object.defineProperty(obj, name, {
+ configurable: false,
+ enumerable: true,
+ get: () => {
+ if (state instanceof LazyValueThunk) {
+ state = state.initializer();
+ }
+ return state;
+ },
+ set: () => {
+ throw new Error("Property is not writable.");
+ },
+ });
+}
+
+/**
+ * Placeholder for a method and its signature.
+ */
+class MethodMetadata {
+ constructor(method, isStatic, returnHub, ...paramHubs) {
+ this.method = method;
+ this.isStatic = isStatic;
+ this.returnHub = returnHub;
+ this.paramHubs = paramHubs;
+ }
+}
+
+/**
+ * Create MethodMetadata object for an instance method.
+ */
+function mmeta(method, returnHub, ...paramHubs) {
+ return new MethodMetadata(method, false, returnHub, ...paramHubs);
+}
+
+/**
+ * Create MethodMetadata object for a static method.
+ */
+function smmeta(method, returnHub, ...paramHubs) {
+ return new MethodMetadata(method, true, returnHub, ...paramHubs);
+}
+
+/**
+ * Describes extra class metadata used by the runtime.
+ */
+class ClassMetadata {
+ /**
+ * Constructs the class metadata.
+ *
+ * @param ft Field table
+ * @param singleAbstractMethod Method metadata for the single abstract method, when the class implements exactly one functional interface
+ * @param methodTable Dictionary mapping each method name to the list of overloaded signatures
+ */
+ constructor(ft, singleAbstractMethod = undefined, methodTable = undefined) {
+ this.ft = ft;
+ this.singleAbstractMethod = singleAbstractMethod;
+ this.methodTable = methodTable;
+ }
+}
+
+/**
+ * Class for the various runtime utilities.
+ */
+class Runtime {
+ constructor() {
+ this.isLittleEndian = false;
+ /**
+ * Dictionary of all initializer functions.
+ */
+ this.jsResourceInits = {};
+ /**
+ * The data object of the current VM, which contains the configuration settings,
+ * optionally a binary-encoded image heap, and other resources.
+ *
+ * The initial data value is present in the enclosing scope.
+ *
+ * @type Data
+ */
+ this.data = null;
+ /**
+ * Map from full Java class names to corresponding Java hubs, for classes that are accessible outside of the image.
+ */
+ this.hubs = {};
+ /**
+ * The table of native functions that can be invoked via indirect calls.
+ *
+ * The index in this table represents the address of the function.
+ * The zero-th entry is always set to null.
+ */
+ this.funtab = [null];
+ /**
+ * Map of internal symbols that are used during execution.
+ */
+ Object.defineProperty(this, "symbol", {
+ writable: false,
+ configurable: false,
+ value: {
+ /**
+ * Symbol used to symbolically get the to-JavaScript-native coercion object on the Java proxy.
+ *
+ * This symbol is available as a property on Java proxies, and will return a special object
+ * that can coerce the Java proxy to various native JavaScript values.
+ *
+ * See the ProxyHandler class for more details.
+ */
+ javaScriptCoerceAs: Symbol("__javascript_coerce_as__"),
+
+ /**
+ * Key used by Web Image to store the corresponding JS native value as a property of JSValue objects.
+ *
+ * Used by the JS annotation.
+ *
+ * Use conversion.setJavaScriptNative and conversion.extractJavaScriptNative to access that property
+ * instead of using this symbol directly.
+ */
+ javaScriptNative: Symbol("__javascript_native__"),
+
+ /**
+ * Key used to store a property value (inside a JavaScript object) that contains the Java-native object.
+ *
+ * Used by the JS annotation.
+ */
+ javaNative: Symbol("__java_native__"),
+
+ /**
+ * Key used to store the runtime-generated proxy handler inside the Java class.
+ *
+ * The handler is created lazily the first time that the corresponding class is added
+ *
+ * Use getOrCreateProxyHandler to retrieve the proxy handler instead of using this symbol directly.
+ */
+ javaProxyHandler: Symbol("__java_proxy_handler__"),
+
+ /**
+ * Key used to store the extra class metadata when emitting Java classes.
+ */
+ classMeta: Symbol("__class_metadata__"),
+
+ /**
+ * Key for the hub-object property that points to the corresponding generated JavaScript class.
+ */
+ jsClass: Symbol("__js_class__"),
+
+ /**
+ * Key for the property on primitive hubs, which points to the corresponding boxed hub.
+ */
+ boxedHub: Symbol("__boxed_hub__"),
+
+ /**
+ * Key for the property on primitive hubs, which holds the boxing function.
+ */
+ box: Symbol("__box__"),
+
+ /**
+ * Key for the property on primitive hubs, which holds the unboxing function.
+ */
+ unbox: Symbol("__unbox__"),
+
+ /**
+ * Key for the constructor-overload list that is stored in the class metadata.
+ */
+ ctor: Symbol("__ctor__"),
+
+ /**
+ * Internal value passed to JavaScript mirror-class constructors
+ * to denote that the mirrored class was instantiated from Java.
+ *
+ * This is used when a JSObject subclass gets constructed from Java.
+ */
+ skipJavaCtor: Symbol("__skip_java_ctor__"),
+
+ /**
+ * Symbol for the Java toString method.
+ */
+ toString: Symbol("__toString__"),
+ },
+ });
+
+ // Conversion-related functions and values.
+ // The following values are set or used by the jsconversion module.
+
+ /**
+ * The holder of JavaScript mirror class for JSObject subclasses.
+ */
+ this.mirrors = {};
+ /**
+ * Reference to the hub of the java.lang.Class class.
+ */
+ this.classHub = null;
+ /**
+ * Function that retrieves the hub of the specified Java object.
+ */
+ this.hubOf = null;
+ /**
+ * Function that checks if the first argument hub is the supertype or the same as the second argument hub.
+ */
+ this.isSupertype = null;
+ /**
+ * Mapping from JavaScript classes that were imported to the list of internal Java classes
+ * under which the corresponding JavaScript class was imported.
+ * See JS.Import annotation.
+ */
+ this.importMap = new Map();
+ }
+
+ /**
+ * Use the build-time endianness at run-time.
+ *
+ * Unsafe operations that write values to byte arrays at build-time assumes the
+ * endianness of the build machine. Therefore, unsafe read and write operations
+ * at run-time need to assume the same endianness.
+ */
+ setEndianness(isLittleEndian) {
+ runtime.isLittleEndian = isLittleEndian;
+ }
+
+ /**
+ * Ensures that there is a Set entry for the given JavaScript class, and returns it.
+ */
+ ensureFacadeSetFor(cls) {
+ let facades = this.importMap.get(cls);
+ if (facades === undefined) {
+ facades = new Set();
+ this.importMap.set(cls, facades);
+ }
+ return facades;
+ }
+
+ /**
+ * Finds the set of Java facade classes for the given JavaScript class, or an empty set if there are none.
+ */
+ findFacadesFor(cls) {
+ let facades = this.importMap.get(cls);
+ if (facades === undefined) {
+ facades = new Set();
+ }
+ return facades;
+ }
+
+ _ensurePackage(container, name) {
+ const elements = name === "" ? [] : name.split(".");
+ let current = container;
+ for (let i = 0; i < elements.length; i++) {
+ const element = elements[i];
+ current = element in current ? current[element] : (current[element] = {});
+ }
+ return current;
+ }
+
+ /**
+ * Get or create the specified exported JavaScript mirror-class export package on the VM object.
+ *
+ * @param name Full Java name of the package
+ */
+ ensureExportPackage(name) {
+ return this._ensurePackage(vm.exports, name);
+ }
+
+ /**
+ * Get or create the specified exported JavaScript mirror-class package on the Runtime object.
+ *
+ * @param name Full Java name of the package
+ */
+ ensureVmPackage(name) {
+ return this._ensurePackage(runtime.mirrors, name);
+ }
+
+ /**
+ * Get an existing exported JavaScript mirror class on the Runtime object.
+ *
+ * @param className Full Java name of the class
+ */
+ vmClass(className) {
+ const elements = className.split(".");
+ let current = runtime.mirrors;
+ for (let i = 0; i < elements.length; i++) {
+ const element = elements[i];
+ if (element in current) {
+ current = current[element];
+ } else {
+ return null;
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Returns the array with all the prefetched library names.
+ */
+ prefetchedLibraryNames() {
+ const names = [];
+ for (const name in this.data.libraries) {
+ names.push(name);
+ }
+ return names;
+ }
+
+ /**
+ * Adds a function to the function table.
+ *
+ * @param f The function to add
+ * @returns {number} The address of the newly added function
+ */
+ addToFuntab(f) {
+ this.funtab.push(f);
+ return runtime.funtab.length - 1;
+ }
+
+ /**
+ * Fetches binary data from the given url.
+ *
+ * @param url
+ * @returns {!Promise<!ArrayBuffer>}
+ */
+ fetchData(url) {
+ return Promise.reject(new Error("fetchData is not supported"));
+ }
+
+ /**
+ * Fetches UTF8 text from the given url.
+ *
+ * @param url
+ * @returns {!Promise<!String>}
+ */
+ fetchText(url) {
+ return Promise.reject(new Error("fetchText is not supported"));
+ }
+
+ /**
+ * Sets the exit code for the VM.
+ */
+ setExitCode(c) {
+ vm.exitCode = c;
+ }
+
+ /**
+ * Returns the absolute path of the JS file WebImage is running in.
+ *
+ * Depending on the runtime, this may be a URL or an absolute filesystem path.
+ * @returns {!String}
+ */
+ getCurrentFile() {
+ throw new Error("getCurrentFile is not supported");
+ }
+}
+
+/**
+ * Instance of the internal runtime state of the VM.
+ */
+const runtime = new Runtime();
+
+/**
+ * VM state that is exposed, and which represents the VM API accessible to external users.
+ */
+class VM {
+ constructor() {
+ this.exitCode = 0;
+ this.exports = {};
+ this.symbol = {};
+ /**
+ * The to-JavaScript-native coercion symbol in the external API.
+ */
+ Object.defineProperty(this.symbol, "as", {
+ configurable: false,
+ enumerable: true,
+ writable: false,
+ value: runtime.symbol.javaScriptCoerceAs,
+ });
+ /**
+ * The symbol for the Java toString method in the external API.
+ */
+ Object.defineProperty(this.symbol, "toString", {
+ configurable: false,
+ enumerable: true,
+ writable: false,
+ value: runtime.symbol.toString,
+ });
+ }
+
+ /**
+ * Coerce the specified JavaScript value to the specified Java type.
+ *
+ * For precise summary of the coercion rules, please see the JS annotation JavaDoc.
+ *
+ * The implementation for this function is injected later.
+ *
+ * @param javaScriptValue The JavaScript value to coerce
+ * @param type The name of the Java class to coerce to, or a Java Proxy representing the target class.
+ * @returns {*} The closest corresponding Java Proxy value
+ */
+ as(javaScriptValue, type) {
+ throw new Error("VM.as is not supported in this backend or it was called too early");
+ }
+}
+
+/**
+ * Instance of the class that represents the public VM API.
+ */
+const vm = new VM();
+
+if (features.node_fs.detected && features.node_https.detected) {
+ runtime.fetchText = (url) => {
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ return new Promise((fulfill, reject) => {
+ let content = [];
+ features.node_https
+ .get()
+ .get(url, (r) => {
+ r.on("data", (data) => {
+ content.push(data);
+ });
+ r.on("end", () => {
+ fulfill(content.join(""));
+ });
+ })
+ .on("error", (e) => {
+ reject(e);
+ });
+ });
+ } else {
+ return features.node_fs.get().readFile(url, "utf8");
+ }
+ };
+ runtime.fetchData = (url) => {
+ return features.node_fs
+ .get()
+ .readFile(url)
+ .then((d) => d.buffer);
+ };
+} else if (features.fetch.detected) {
+ runtime.fetchText = (url) =>
+ features.fetch
+ .get()(url)
+ .then((r) => r.text());
+ runtime.fetchData = (url) =>
+ features.fetch
+ .get()(url)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`Failed to load data at '${url}': ${response.status} ${response.statusText}`);
+ }
+ return response.arrayBuffer();
+ });
+}
+
+if (features.node_process.detected) {
+ // Extend the setExitCode function to also set the exit code of the runtime.
+ let oldFun = runtime.setExitCode;
+ runtime.setExitCode = (exitCode) => {
+ oldFun(exitCode);
+ features.node_process.get().exitCode = exitCode;
+ };
+}
+
+if (features.filename.detected) {
+ runtime.getCurrentFile = () => features.filename.data.filename;
+} else if (features.currentScript.detected) {
+ runtime.getCurrentFile = () => features.currentScript.data.currentScript.src;
+} else if (features.location.detected) {
+ runtime.getCurrentFile = () => features.location.data.location.href;
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Imports object passed to the WASM module during instantiation.
+ *
+ * @see WasmImports
+ */
+const wasmImports = {};
+
+/**
+ * Imports for operations that cannot be performed (or be easily emulated) in WASM.
+ */
+wasmImports.compat = {
+ f64rem: (x, y) => x % y,
+ f64log: Math.log,
+ f64log10: Math.log10,
+ f64sin: Math.sin,
+ f64cos: Math.cos,
+ f64tan: Math.tan,
+ f64tanh: Math.tanh,
+ f64exp: Math.exp,
+ f64pow: Math.pow,
+ f32rem: (x, y) => x % y,
+};
+
+/**
+ * Imports relating to I/O.
+ */
+wasmImports.io = {};
+
+/**
+ * Loads and instantiates the appropriate WebAssembly module.
+ *
+ * The module path is given by config.wasm_path, if specified, otherwise it is loaded relative to the current script file.
+ */
+async function wasmInstantiate(config, args) {
+ // const wasmPath = config.wasm_path || runtime.getCurrentFile() + ".wasm";
+ const wasmPath = "js/cljcc-lib-wasm.js.wasm";
+ const file = await runtime.fetchData(wasmPath);
+ const result = await WebAssembly.instantiate(file, wasmImports);
+ return {
+ instance: result.instance,
+ memory: result.instance.exports.memory,
+ };
+}
+
+/**
+ * Runs the main entry point of the given WebAssembly module.
+ */
+function wasmRun(args) {
+ try {
+ doRun(args);
+ } catch (e) {
+ console.log("Uncaught internal error:");
+ console.log(e);
+ runtime.setExitCode(1);
+ }
+}
+
+function getExports() {
+ return runtime.data.wasm.instance.exports;
+}
+
+function getExport(name) {
+ return getExports()[name];
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Constructs Java string from JavaScript string.
+ */
+function toJavaString(jsStr) {
+ const length = jsStr.length;
+ const charArray = getExport("array.char.create")(length);
+
+ for (let i = 0; i < length; i++) {
+ getExport("array.char.write")(charArray, i, jsStr.charCodeAt(i));
+ }
+
+ return getExport("string.fromchars")(charArray);
+}
+
+/**
+ * Constructs a Java string array (String[]) from a JavaScript array of
+ * JavaScript strings.
+ */
+function toJavaStringArray(jsStrings) {
+ const length = jsStrings.length;
+ const stringArray = getExport("array.string.create")(length);
+
+ for (let i = 0; i < length; i++) {
+ getExport("array.object.write")(stringArray, i, toJavaString(jsStrings[i]));
+ }
+
+ return stringArray;
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Checks if the given string is an array index.
+ *
+ * Proxied accesses always get a string property (even for indexed accesses) so
+ * we need to check if the property access is an indexed access.
+ *
+ * A property is an index if the numeric index it is refering to has the same
+ * string representation as the original property.
+ * E.g the string '010' is a property while '10' is an index.
+ */
+function isArrayIndex(property) {
+ try {
+ return Number(property).toString() === property;
+ } catch (e) {
+ // Catch clause because not all property keys (e.g. symbols) can be
+ // converted to a number.
+ return false;
+ }
+}
+
+/**
+ * Proxy handler that proxies all array element and length accesses to a Wasm
+ * array and everything else to the underlying array.
+ *
+ * See `proxyArray` function for more information.
+ */
+class ArrayProxyHandler {
+ /**
+ * Reference to the Wasm-world object.
+ *
+ * In this case, this will be a Wasm struct containing a Wasm array.
+ */
+ #wasmObject;
+
+ /**
+ * Immutable length of the array, determined during construction.
+ */
+ #length;
+
+ /**
+ * A callable (usually an exported function) that accepts the Wasm object
+ * and an index and returns the element at that location.
+ */
+ #reader;
+
+ constructor(wasmObject, reader) {
+ this.#wasmObject = wasmObject;
+ this.#length = getExport("array.length")(wasmObject);
+ this.#reader = reader;
+ }
+
+ #isInBounds(idx) {
+ return idx >= 0 && idx < this.#length;
+ }
+
+ #getElement(idx) {
+ /*
+ * We need an additional bounds check here because Wasm will trap,
+ * while JS expects an undefined value.
+ */
+ if (this.#isInBounds(idx)) {
+ return this.#reader(this.#wasmObject, idx);
+ } else {
+ return undefined;
+ }
+ }
+
+ defineProperty() {
+ throw new TypeError("This array is immutable. Attempted to call defineProperty");
+ }
+
+ deleteProperty() {
+ throw new TypeError("This array is immutable. Attempted to call deleteProperty");
+ }
+
+ /**
+ * Indexed accesses and the `length` property are serviced from the Wasm
+ * object, everything else goes to the underlying object.
+ */
+ get(target, property, receiver) {
+ if (isArrayIndex(property)) {
+ return this.#getElement(property);
+ } else if (property == "length") {
+ return this.#length;
+ } else {
+ return Reflect.get(target, property, receiver);
+ }
+ }
+
+ getOwnPropertyDescriptor(target, property) {
+ if (isArrayIndex(property)) {
+ return {
+ value: this.#getElement(property),
+ writable: false,
+ enumerable: true,
+ configurable: false,
+ };
+ } else {
+ return Reflect.getOwnPropertyDescriptor(target, property);
+ }
+ }
+
+ has(target, property) {
+ if (isArrayIndex(property)) {
+ return this.#isInBounds(Number(property));
+ } else {
+ return Reflect.has(target, property);
+ }
+ }
+
+ isExtensible() {
+ return false;
+ }
+
+ /**
+ * Returns the array's own enumerable string-keyed property names.
+ *
+ * For arrays this is simply an array of all indices in string form.
+ */
+ ownKeys() {
+ return Object.keys(Array.from({ length: this.#length }, (x, i) => i));
+ }
+
+ preventExtensions() {
+ // Do nothing this object is already not extensible
+ }
+
+ set() {
+ throw new TypeError("This array is immutable. Attempted to call set");
+ }
+
+ setPrototypeOf() {
+ throw new TypeError("This array is immutable. Attempted to call setPrototypeOf");
+ }
+}
+
+/**
+ * Creates a read-only view on a Wasm array that looks like a JavaScript Array.
+ *
+ * The proxy is backed by an Array instance (albeit an empty one) and any
+ * accesses except for `length` and indexed accesses (see `isArrayIndex`) are
+ * proxied to the original object.
+ * Because the `Array.prototype` methods are all generic and only access
+ * `length` and the indices, this works. For example if `indexOf` is called on
+ * the proxy, `Array.prototype.indexOf` is called with the proxy bound to
+ * `this`, thus the `indexOf` implementation goes through the proxy when
+ * accessing elements or determining the array length.
+ */
+function proxyArray(a, reader) {
+ return new Proxy(new Array(), new ArrayProxyHandler(a, reader));
+}
+
+function proxyCharArray(a) {
+ return proxyArray(a, getExport("array.char.read"));
+}
+
+wasmImports.convert = {};
+wasmImports.convert.proxyCharArray = proxyCharArray;
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+function doRun(args) {
+ getExport("main")(toJavaStringArray(args));
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+const STACK_TRACE_MARKER = "NATIVE-IMAGE-MARKER";
+
+/**
+ * Create JavaScript Error object, which is used to fill in the Throwable.backtrace object.
+ */
+function genBacktrace() {
+ return new Error(STACK_TRACE_MARKER);
+}
+
+/**
+ * Extract a Java string from the given backtrace object, which is supposed to be a JavaScript Error object.
+ */
+function formatStackTrace(backtrace) {
+ let trace;
+
+ if (backtrace.stack) {
+ let lines = backtrace.stack.split("\n");
+
+ /*
+ * Since Error.prototype.stack is non-standard, different runtimes set
+ * it differently.
+ * We try to remove the preamble that contains the error name and
+ * message to just get the stack trace.
+ */
+ if (lines.length > 0 && lines[0].includes(STACK_TRACE_MARKER)) {
+ lines = lines.splice(1);
+ }
+
+ trace = lines.join("\n");
+ } else {
+ trace = "This JavaScript runtime does not expose stack trace information.";
+ }
+
+ return toJavaString(trace);
+}
+
+function gen_call_stack() {
+ return formatStackTrace(genBacktrace());
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Provides the current working directory from the Config instance to Java code.
+ */
+function getCurrentWorkingDirectory() {
+ return toJavaString(runtime.data.config.currentWorkingDirectory);
+}
+runtime.setEndianness(1);
+wasmImports.interop = {
+ 'Date.now' : (...args) => Date.now(...args),
+ 'formatStackTrace' : (...args) => formatStackTrace(...args),
+ 'genBacktrace' : (...args) => genBacktrace(...args),
+ 'getCurrentWorkingDirectory' : (...args) => getCurrentWorkingDirectory(...args),
+ 'llog' : (...args) => llog(...args),
+ 'performance.now' : (...args) => performance.now(...args),
+ 'runtime.setExitCode' : (...args) => runtime.setExitCode(...args),
+ 'stderrWriter.close' : (...args) => stderrWriter.close(...args),
+ 'stderrWriter.flush' : (...args) => stderrWriter.flush(...args),
+ 'stderrWriter.printChars' : (...args) => stderrWriter.printChars(...args),
+ 'stdoutWriter.close' : (...args) => stdoutWriter.close(...args),
+ 'stdoutWriter.flush' : (...args) => stdoutWriter.flush(...args),
+ 'stdoutWriter.printChars' : (...args) => stdoutWriter.printChars(...args),
+}
+;
+wasmImports.jsbody = {
+ '_JSBigInt.javaString___String' : (...args) => (function(){
+ try{
+ return conversion.toProxy(toJavaString(this.toString()));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSBoolean.javaBoolean___Boolean' : (...args) => (function(){
+ try{
+ return conversion.toProxy(conversion.createJavaBoolean(this));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.asJavaObjectOrString___Object_Object' : (...args) => (function(obj){
+ try{
+ return conversion.isInternalJavaObject(obj) ? obj : toJavaString(obj.toString());
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.extractJavaScriptProxy___Object_Object' : (...args) => (function(self){
+ try{
+ return conversion.toProxy(self);
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.extractJavaScriptString___String_Object' : (...args) => (function(s){
+ try{
+ return conversion.extractJavaScriptString(s);
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.javaScriptToJava___Object_Object' : (...args) => (function(x){
+ try{
+ return conversion.javaScriptToJava(x);
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.javaScriptUndefined___Object' : (...args) => (function(){
+ try{
+ return undefined;
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSConversion.unproxy___Object_Object' : (...args) => (function(proxy){
+ try{
+ const javaNative = proxy[runtime.symbol.javaNative]; return javaNative === undefined ? null : javaNative;
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSNumber.javaDouble___Double' : (...args) => (function(){
+ try{
+ return conversion.toProxy(conversion.createJavaDouble(this));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSObject.get___Object_Object' : (...args) => (function(key){
+ try{
+ return this[key];
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSObject.stringValue___String' : (...args) => (function(){
+ try{
+ return conversion.toProxy(toJavaString(this.toString()));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSObject.typeofString___JSString' : (...args) => (function(){
+ try{
+ return typeof this;
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSString.javaString___String' : (...args) => (function(){
+ try{
+ return conversion.toProxy(toJavaString(this));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSSymbol.javaString___String' : (...args) => (function(){
+ try{
+ return conversion.toProxy(toJavaString(this.toString()));
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_JSSymbol.referenceEquals___JSSymbol_JSSymbol_JSBoolean' : (...args) => (function(sym0,sym1){
+ try{
+ return sym0 === sym1;
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+ '_WebImageUtil.random___D' : (...args) => (function(){
+ try{
+ return Math.random();
+ }catch( e ) {
+ conversion.handleJSError(e);}}).call(...args),
+}
+;
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Code dealing with moving values between the Java and JavaScript world.
+ *
+ * This code handles Java values:
+ * - All Java values except for objects and longs are represented as JS Number values.
+ * - Object and long representation depends on the backend used.
+ * Variables and arguments representing Java values are usually marked explicitly
+ * (e.g. by being named something like javaObject or jlstring, which stands for
+ * java.lang.String).
+ *
+ * Java values can be used to call Java methods directly without additional
+ * conversions, which is the basis of the functionality this class provides.
+ * It facilitates calling Java methods from JavaScript by first performing the
+ * necessary conversions or coercions before the Java call is executed. In the
+ * reverse direction, it helps Java code execute JS code (e.g. through the @JS
+ * annotation) by converting or coercing Java values into appropriate JS values.
+ */
+class Conversion {
+ /**
+ * Associates the given Java object with the given JS value.
+ */
+ setJavaScriptNative(javaObject, jsNative) {
+ throw new Error("Unimplemented: Conversion.javaScriptNative");
+ }
+
+ /**
+ * Returns the JS value associated with the given Java object or null if there is no associated value.
+ */
+ extractJavaScriptNative(javaObject) {
+ throw new Error("Unimplemented: Conversion.extractJavaScriptNative");
+ }
+
+ // Java-to-JavaScript conversions
+
+ /**
+ * Given a Java boxed Double, creates the corresponding JavaScript number value.
+ *
+ * @param jldouble The java.lang.Double object
+ * @return {*} A JavaScript Number value
+ */
+ extractJavaScriptNumber(jldouble) {
+ throw new Error("Unimplemented: Conversion.extractJavaScriptNumber");
+ }
+
+ /**
+ * Given a Java String, creates the corresponding JavaScript string value.
+ *
+ * Note: the Java method called in this implementation will return (in the generated code)
+ * an actual primitive Java string.
+ *
+ * @param jlstring The java.lang.String object
+ * @return {*} A JavaScript String value
+ */
+ extractJavaScriptString(jlstring) {
+ throw new Error("Unimplemented: Conversion.extractJavaScriptString");
+ }
+
+ /**
+ * Converts a Java array to a JavaScript array that contains JavaScript values
+ * that correspond to the Java values of the input array.
+ *
+ * @param jarray A Java array
+ * @returns {*} The resulting JavaScript array
+ */
+ extractJavaScriptArray(jarray) {
+ throw new Error("Unimplemented: Conversion.extractJavaScriptArray");
+ }
+
+ // JavaScript-to-Java conversions (standard Java classes)
+
+ /**
+ * Creates a java.lang.Boolean object from a JavaScript boolean value.
+ */
+ createJavaBoolean(b) {
+ throw new Error("Unimplemented: Conversion.createJavaBoolean");
+ }
+
+ /**
+ * Creates a java.lang.Byte object from a JavaScript number value.
+ */
+ createJavaByte(x) {
+ throw new Error("Unimplemented: Conversion.createJavaByte");
+ }
+
+ /**
+ * Creates a java.lang.Short object from a JavaScript number value.
+ */
+ createJavaShort(x) {
+ throw new Error("Unimplemented: Conversion.createJavaShort");
+ }
+
+ /**
+ * Creates a java.lang.Character object from a JavaScript number value.
+ */
+ createJavaCharacter(x) {
+ throw new Error("Unimplemented: Conversion.createJavaCharacter");
+ }
+
+ /**
+ * Creates a java.lang.Integer object from a JavaScript number value.
+ */
+ createJavaInteger(x) {
+ throw new Error("Unimplemented: Conversion.createJavaInteger");
+ }
+
+ /**
+ * Creates a java.lang.Float object from a JavaScript number value.
+ */
+ createJavaFloat(x) {
+ throw new Error("Unimplemented: Conversion.createJavaFloat");
+ }
+
+ /**
+ * Creates a java.lang.Long object from a JavaScript number value.
+ */
+ createJavaLong(x) {
+ throw new Error("Unimplemented: Conversion.createJavaLong");
+ }
+
+ /**
+ * Creates a java.lang.Double object from a JavaScript number value.
+ */
+ createJavaDouble(x) {
+ throw new Error("Unimplemented: Conversion.createJavaDouble");
+ }
+
+ /**
+ * Gets the JavaKind ordinal for the given hub, as expected by `boxIfNeeded`.
+ */
+ getHubKindOrdinal(hub) {
+ throw new Error("Unimplemented: Conversion.getHubKindOrdinal");
+ }
+
+ /**
+ * Box the given value if the specified type is primitive.
+ *
+ * The parameter type is the enum index as defined in jdk.vm.ci.meta.JavaKind.
+ * The following is a summary:
+ *
+ * 0 - Boolean
+ * 1 - Byte
+ * 2 - Short
+ * 3 - Char
+ * 4 - Int
+ * 5 - Float
+ * 6 - Long
+ * 7 - Double
+ * 8 - Object
+ *
+ * @param {number=} type
+ */
+ boxIfNeeded(javaValue, type) {
+ switch (type) {
+ case 0:
+ return this.createJavaBoolean(javaValue);
+ case 1:
+ return this.createJavaByte(javaValue);
+ case 2:
+ return this.createJavaShort(javaValue);
+ case 3:
+ return this.createJavaCharacter(javaValue);
+ case 4:
+ return this.createJavaInteger(javaValue);
+ case 5:
+ return this.createJavaFloat(javaValue);
+ case 6:
+ return this.createJavaLong(javaValue);
+ case 7:
+ return this.createJavaDouble(javaValue);
+ default:
+ return javaValue;
+ }
+ }
+
+ /**
+ * Unbox the given value if the specified type is primitive.
+ *
+ * See documentation for `boxIfNeeded`.
+ */
+ unboxIfNeeded(javaObject, type) {
+ switch (type) {
+ case 0:
+ return this.unboxBoolean(javaObject);
+ case 1:
+ return this.unboxByte(javaObject);
+ case 2:
+ return this.unboxShort(javaObject);
+ case 3:
+ return this.unboxChar(javaObject);
+ case 4:
+ return this.unboxInt(javaObject);
+ case 5:
+ return this.unboxFloat(javaObject);
+ case 6:
+ return this.unboxLong(javaObject);
+ case 7:
+ return this.unboxDouble(javaObject);
+ default:
+ return javaObject;
+ }
+ }
+
+ unboxBoolean(jlBoolean) {
+ throw new Error("Unimplemented: Conversion.unboxBoolean");
+ }
+
+ unboxByte(jlByte) {
+ throw new Error("Unimplemented: Conversion.unboxByte");
+ }
+
+ unboxShort(jlShort) {
+ throw new Error("Unimplemented: Conversion.unboxShort");
+ }
+
+ unboxChar(jlChar) {
+ throw new Error("Unimplemented: Conversion.unboxChar");
+ }
+
+ unboxInt(jlInt) {
+ throw new Error("Unimplemented: Conversion.unboxInt");
+ }
+
+ unboxFloat(jlFloat) {
+ throw new Error("Unimplemented: Conversion.unboxFloat");
+ }
+
+ unboxLong(jlLong) {
+ throw new Error("Unimplemented: Conversion.unboxLong");
+ }
+
+ unboxDouble(jlDouble) {
+ throw new Error("Unimplemented: Conversion.unboxDouble");
+ }
+
+ /**
+ * Gets the boxed counterpart of the given primitive hub.
+ */
+ getBoxedHub(jlClass) {
+ throw new Error("Unimplemented: Conversion.getBoxedHub");
+ }
+
+ // JavaScript-to-Java conversions (JSValue classes)
+
+ /**
+ * Gets the Java singleton object that represents the JavaScript undefined value.
+ */
+ createJSUndefined() {
+ throw new Error("Unimplemented: Conversion.createJSUndefined");
+ }
+
+ /**
+ * Wraps a JavaScript Boolean into a Java JSBoolean object.
+ *
+ * @param boolean The JavaScript boolean to wrap
+ * @return {*} The Java JSBoolean object
+ */
+ createJSBoolean(boolean) {
+ throw new Error("Unimplemented: Conversion.createJSBoolean");
+ }
+
+ /**
+ * Wraps a JavaScript Number into a Java JSNumber object.
+ *
+ * @param number The JavaScript number to wrap
+ * @return {*} The Java JSNumber object
+ */
+ createJSNumber(number) {
+ throw new Error("Unimplemented: Conversion.createJSNumber");
+ }
+
+ /**
+ * Wraps a JavaScript BigInt into a Java JSBigInt object.
+ *
+ * @param bigint The JavaScript BigInt value to wrap
+ * @return {*} The Java JSBigInt object
+ */
+ createJSBigInt(bigint) {
+ throw new Error("Unimplemented: Conversion.createJSBigInt");
+ }
+
+ /**
+ * Wraps a JavaScript String into a Java JSString object.
+ *
+ * @param string The JavaScript String value to wrap
+ * @return {*} The Java JSString object
+ */
+ createJSString(string) {
+ throw new Error("Unimplemented: Conversion.createJSString");
+ }
+
+ /**
+ * Wraps a JavaScript Symbol into a Java JSSymbol object.
+ *
+ * @param symbol The JavaScript Symbol value to wrap
+ * @return {*} The Java JSSymbol object
+ */
+ createJSSymbol(symbol) {
+ throw new Error("Unimplemented: Conversion.createJSSymbol");
+ }
+
+ /**
+ * Wraps a JavaScript object into a Java JSObject object.
+ *
+ * @param obj The JavaScript Object value to wrap
+ * @returns {*} The Java JSObject object
+ */
+ createJSObject(obj) {
+ throw new Error("Unimplemented: Conversion.createJSObject");
+ }
+
+ // Helper methods
+
+ /**
+ * Checks if the specified object (which may be a JavaScript value or a Java value) is an internal Java object.
+ */
+ isInternalJavaObject(obj) {
+ throw new Error("Unimplemented: Conversion.isInternalJavaObject");
+ }
+
+ isPrimitiveHub(hub) {
+ throw new Error("Unimplemented: Conversion.isPrimitiveHub");
+ }
+
+ isJavaLangString(obj) {
+ throw new Error("Unimplemented: Conversion.isJavaLangString");
+ }
+
+ isJavaLangClass(obj) {
+ throw new Error("Unimplemented: Conversion.isJavaLangClassHub");
+ }
+
+ isInstance(obj, hub) {
+ throw new Error("Unimplemented: Conversion.isInstance");
+ }
+
+ /**
+ * Copies own fields from source to destination.
+ *
+ * Existing fields in the destination are overwritten.
+ */
+ copyOwnFields(src, dst) {
+ for (let name of Object.getOwnPropertyNames(src)) {
+ dst[name] = src[name];
+ }
+ }
+
+ /**
+ * Creates an anonymous JavaScript object, and does the mirror handshake.
+ */
+ createAnonymousJavaScriptObject() {
+ const x = {};
+ const jsObject = this.createJSObject(x);
+ x[runtime.symbol.javaNative] = jsObject;
+ return x;
+ }
+
+ /**
+ * Obtains or creates the proxy handler for the given Java class
+ */
+ getOrCreateProxyHandler(arg) {
+ throw new Error("Unimplemented: Conversion.getOrCreateProxyHandler");
+ }
+
+ /**
+ * For proxying the given object returns value that should be passed to
+ * getOrCreateProxyHandler.
+ */
+ _getProxyHandlerArg(obj) {
+ throw new Error("Unimplemented: Conversion._getProxyHandlerArg");
+ }
+
+ /**
+ * Creates a proxy that intercepts messages that correspond to Java method calls and Java field accesses.
+ *
+ * @param obj The Java object to create a proxy for
+ * @return {*} The proxy around the Java object
+ */
+ toProxy(obj) {
+ let proxyHandler = this.getOrCreateProxyHandler(this._getProxyHandlerArg(obj));
+ // The wrapper is a temporary object that allows having the non-identifier name of the target function.
+ // We declare the property as a function, to ensure that it is constructable, so that the Proxy handler's construct method is callable.
+ let targetWrapper = {
+ ["Java Proxy"]: function (key) {
+ if (key === runtime.symbol.javaNative) {
+ return obj;
+ }
+ return undefined;
+ },
+ };
+
+ return new Proxy(targetWrapper["Java Proxy"], proxyHandler);
+ }
+
+ /**
+ * Converts a JavaScript value to the corresponding Java representation.
+ *
+ * The exact rules of the mapping are documented in the Java JS annotation class.
+ *
+ * This method is only meant to be called from the conversion code generated for JS-annotated methods.
+ *
+ * @param x The JavaScript value to convert
+ * @return {*} The Java representation of the JavaScript value
+ */
+ javaScriptToJava(x) {
+ // Step 1: check null, which is mapped 1:1 to null in Java.
+ if (x === null) {
+ return null;
+ }
+
+ // Step 2: check undefined, which is a singleton in Java.
+ if (x === undefined) {
+ return this.createJSUndefined();
+ }
+
+ // Step 3: check if the javaNative property is set.
+ // This covers objects that already have Java counterparts (for example, Java proxies).
+ const javaValue = x[runtime.symbol.javaNative];
+ if (javaValue !== undefined) {
+ return javaValue;
+ }
+
+ // Step 4: use the JavaScript type to select the appropriate Java representation.
+ const tpe = typeof x;
+ switch (tpe) {
+ case "boolean":
+ return this.createJSBoolean(x);
+ case "number":
+ return this.createJSNumber(x);
+ case "bigint":
+ return this.createJSBigInt(x);
+ case "string":
+ return this.createJSString(x);
+ case "symbol":
+ return this.createJSSymbol(x);
+ case "object":
+ case "function":
+ // We know this is a normal object created in JavaScript,
+ // otherwise it would have a runtime.symbol.javaNative property,
+ // and the conversion would have returned in Step 3.
+ return this.createJSObject(x);
+ default:
+ throw new Error("unexpected type: " + tpe);
+ }
+ }
+
+ /**
+ * Maps each JavaScript value in the input array to a Java value.
+ * See {@code javaScriptToJava}.
+ */
+ eachJavaScriptToJava(javaScriptValues) {
+ const javaValues = new Array(javaScriptValues.length);
+ for (let i = 0; i < javaScriptValues.length; i++) {
+ javaValues[i] = this.javaScriptToJava(javaScriptValues[i]);
+ }
+ return javaValues;
+ }
+
+ /**
+ * Converts a Java value to JavaScript.
+ */
+ javaToJavaScript(x) {
+ throw new Error("Unimplemented: Conversion.javaToJavaScript");
+ }
+
+ throwClassCastExceptionImpl(javaObject, tpeNameJavaString) {
+ throw new Error("Unimplemented: Conversion.throwClassCastExceptionImpl");
+ }
+
+ throwClassCastException(javaObject, tpe) {
+ let tpeName;
+ if (typeof tpe === "string") {
+ tpeName = tpe;
+ } else if (typeof tpe === "function") {
+ tpeName = tpe.name;
+ } else {
+ tpeName = tpe.toString();
+ }
+ this.throwClassCastExceptionImpl(javaObject, toJavaString(tpeName));
+ }
+
+ /**
+ * Converts the specified Java Proxy to the target JavaScript type, if possible.
+ *
+ * This method is meant to be called from Java Proxy object, either when implicit coercion is enabled,
+ * or when the user explicitly invokes coercion on the Proxy object.
+ *
+ * @param proxyHandler handler for the proxy that must be converted
+ * @param proxy the Java Proxy object that should be coerced
+ * @param tpe target JavaScript type name (result of the typeof operator) or constructor function
+ * @return {*} the resulting JavaScript value
+ */
+ coerceJavaProxyToJavaScriptType(proxyHandler, proxy, tpe) {
+ throw new Error("Unimplemented: Conversion.coerceJavaProxyToJavaScriptType");
+ }
+
+ /**
+ * Try to convert the JavaScript object to a Java facade class, or return null.
+ *
+ * @param obj JavaScript object whose Java facade class we search for
+ * @param cls target Java class in the form of its JavaScript counterpart
+ * @return {*} the mirror instance wrapped into a JavaScript Java Proxy, or null
+ */
+ tryExtractFacadeClass(obj, cls) {
+ const facades = runtime.findFacadesFor(obj.constructor);
+ const rawJavaHub = cls[runtime.symbol.javaNative];
+ const internalJavaClass = rawJavaHub[runtime.symbol.jsClass];
+ if (facades.has(internalJavaClass)) {
+ const rawJavaMirror = new internalJavaClass();
+ // Note: only one-way handshake, since the JavaScript object could be recast to a different Java facade class.
+ this.setJavaScriptNative(rawJavaMirror, obj);
+ return this.toProxy(rawJavaMirror);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Coerce the specified JavaScript value to the specified Java type.
+ *
+ * See VM.as for the specification of this function.
+ */
+ coerceJavaScriptToJavaType(javaScriptValue, type) {
+ throw new Error("Unimplemented: Conversion.coerceJavaScriptToJavaType");
+ }
+}
+
+/**
+ * Handle for proxying Java objects.
+ *
+ * Client JS code never directly sees Java object, instead they see proxies
+ * using this handler. The handler is generally specialized per type.
+ * It provides access to the underlying Java methods.
+ *
+ * It also supports invoking the proxy, which calls the single abstract method
+ * in the Java object if available, and for Class objects the new operator
+ * works, creating a Java object and invoking a matching constructor.
+ *
+ * The backends provide method metadata describing the Java methods available
+ * to the proxy. At runtime, when a method call is triggered (a method is
+ * accessed and called, the proxy itself is invoked, or a constructor is called),
+ * the proxy will find a matching implementation based on the types of the
+ * passed arguments.
+ * Arguments and return values are automatically converted to and from Java
+ * objects respectively, but no coercion is done.
+ */
+class ProxyHandler {
+ constructor() {
+ this._initialized = false;
+ this._methods = {};
+ this._staticMethods = {};
+ this._javaConstructorMethod = null;
+ }
+
+ ensureInitialized() {
+ if (!this._initialized) {
+ this._initialized = true;
+ // Function properties derived from accessible Java methods.
+ this._createProxyMethods();
+ // Default function properties.
+ this._createDefaultMethods();
+ }
+ }
+
+ _getMethods() {
+ this.ensureInitialized();
+ return this._methods;
+ }
+
+ _getStaticMethods() {
+ this.ensureInitialized();
+ return this._staticMethods;
+ }
+
+ _getJavaConstructorMethod() {
+ this.ensureInitialized();
+ return this._javaConstructorMethod;
+ }
+
+ /**
+ * Returns a ClassMetadata instance for the class this proxy handler represents.
+ */
+ _getClassMetadata() {
+ throw new Error("Unimplemented: ProxyHandler._getClassMetadata");
+ }
+
+ _getMethodTable() {
+ const classMeta = this._getClassMetadata();
+ if (classMeta === undefined) {
+ return undefined;
+ }
+ return classMeta.methodTable;
+ }
+
+ /**
+ * String that can be printed as part of the toString and valueOf functions.
+ */
+ _getClassName() {
+ throw new Error("Unimplemented: ProxyHandler._getClassName");
+ }
+
+ /**
+ * Link the methods object to the prototype chain of the methods object of the superclass' proxy handler.
+ */
+ _linkMethodPrototype() {
+ throw new Error("Unimplemented: ProxyHandler._linkMethodPrototype");
+ }
+
+ _createProxyMethods() {
+ // Create proxy methods for the current class.
+ const methodTable = this._getMethodTable();
+ if (methodTable === undefined) {
+ return;
+ }
+
+ const proxyHandlerThis = this;
+ for (const name in methodTable) {
+ const overloads = methodTable[name];
+ const instanceOverloads = [];
+ const staticOverloads = [];
+ for (const m of overloads) {
+ if (m.isStatic) {
+ staticOverloads.push(m);
+ } else {
+ instanceOverloads.push(m);
+ }
+ }
+ if (instanceOverloads.length > 0) {
+ this._methods[name] = function (...javaScriptArgs) {
+ // Note: the 'this' value is bound to the Proxy object.
+ return proxyHandlerThis._invokeProxyMethod(name, instanceOverloads, this, ...javaScriptArgs);
+ };
+ }
+ if (staticOverloads.length > 0) {
+ this._staticMethods[name] = function (...javaScriptArgs) {
+ // Note: the 'this' value is bound to the Proxy object.
+ return proxyHandlerThis._invokeProxyMethod(name, staticOverloads, null, ...javaScriptArgs);
+ };
+ }
+ }
+ if (methodTable[runtime.symbol.ctor] !== undefined) {
+ const overloads = methodTable[runtime.symbol.ctor];
+ this._javaConstructorMethod = function (javaScriptJavaProxy, ...javaScriptArgs) {
+ // Note: the 'this' value is bound to the Proxy object.
+ return proxyHandlerThis._invokeProxyMethod("<init>", overloads, javaScriptJavaProxy, ...javaScriptArgs);
+ };
+ } else {
+ this._javaConstructorMethod = function (javaScriptJavaProxy, ...javaScriptArgs) {
+ throw new Error(
+ "Cannot invoke the constructor. Make sure that the constructors are explicitly added to the image."
+ );
+ };
+ }
+
+ this._linkMethodPrototype();
+ }
+
+ /**
+ * Checks whether the given argument values can be used to call the method identified by the given metdata class.
+ */
+ _conforms(args, metadata) {
+ if (metadata.paramHubs.length !== args.length) {
+ return false;
+ }
+ for (let i = 0; i < args.length; i++) {
+ const arg = args[i];
+ let paramHub = metadata.paramHubs[i];
+ if (paramHub === null) {
+ // A null parameter hub means that the type-check always passes.
+ continue;
+ }
+ if (conversion.isPrimitiveHub(paramHub)) {
+ // A primitive hub must be replaced with the hub of the corresponding boxed type.
+ paramHub = conversion.getBoxedHub(paramHub);
+ }
+ if (!conversion.isInstance(arg, paramHub)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ _unboxJavaArguments(args, metadata) {
+ // Precondition -- method metadata refers to a method with a correct arity.
+ for (let i = 0; i < args.length; i++) {
+ const paramHub = metadata.paramHubs[i];
+ args[i] = conversion.unboxIfNeeded(args[i], conversion.getHubKindOrdinal(paramHub));
+ }
+ }
+
+ _createDefaultMethods() {
+ if (!this._methods.hasOwnProperty("toString")) {
+ // The check must use hasOwnProperty, because toString always exists in the prototype.
+ this._methods["toString"] = () => "[Java Proxy: " + this._getClassName() + "]";
+ } else {
+ const javaToString = this._methods["toString"];
+ this._methods[runtime.symbol.toString] = javaToString;
+ this._methods["toString"] = function () {
+ // The `this` value must be bound to the proxy instance.
+ //
+ // The `toString` method is used often in JavaScript, and treated specially.
+ // If its return type is a Java String, then that string is converted to a JavaScript string.
+ // In other words, if the result of the call is a JavaScript proxy (see _invokeProxyMethod return value),
+ // then proxies that represent java.lang.String are converted to JavaScript strings.
+ const javaScriptResult = javaToString.call(this);
+ if (typeof javaScriptResult === "function" || typeof javaScriptResult === "object") {
+ const javaResult = javaScriptResult[runtime.symbol.javaNative];
+ if (javaResult !== undefined && conversion.isJavaLangString(javaResult)) {
+ return conversion.extractJavaScriptString(javaResult);
+ } else {
+ return javaScriptResult;
+ }
+ } else {
+ return javaScriptResult;
+ }
+ };
+ }
+
+ // Override Java methods that return valueOf.
+ // JavaScript requires that valueOf returns a JavaScript primitive (in this case, string).
+ this._methods["valueOf"] = () => "[Java Proxy: " + this._getClassName() + "]";
+
+ const proxyHandlerThis = this;
+ const asProperty = function (tpe) {
+ // Note: this will be bound to the Proxy object.
+ return conversion.coerceJavaProxyToJavaScriptType(proxyHandlerThis, this, tpe);
+ };
+ if (!("$as" in this._methods)) {
+ this._methods["$as"] = asProperty;
+ }
+ this._methods[runtime.symbol.javaScriptCoerceAs] = asProperty;
+
+ const vmProperty = vm;
+ if (!("$vm" in this._methods)) {
+ this._methods["$vm"] = vmProperty;
+ }
+ }
+
+ _loadMethod(target, key) {
+ const member = this._getMethods()[key];
+ if (member !== undefined) {
+ return member;
+ }
+ }
+
+ _methodNames() {
+ return Object.keys(this._getMethods());
+ }
+
+ _invokeProxyMethod(name, overloads, javaScriptJavaProxy, ...javaScriptArgs) {
+ // For static methods, javaScriptThis is set to null.
+ const isStatic = javaScriptJavaProxy === null;
+ const javaThis = isStatic ? null : javaScriptJavaProxy[runtime.symbol.javaNative];
+ const javaArgs = conversion.eachJavaScriptToJava(javaScriptArgs);
+ for (let i = 0; i < overloads.length; i++) {
+ const metadata = overloads[i];
+ if (this._conforms(javaArgs, metadata)) {
+ // Where necessary, perform unboxing of Java arguments.
+ this._unboxJavaArguments(javaArgs, metadata);
+ let javaResult;
+ try {
+ if (isStatic) {
+ javaResult = metadata.method.call(null, ...javaArgs);
+ } else {
+ javaResult = metadata.method.call(null, javaThis, ...javaArgs);
+ }
+ } catch (error) {
+ throw conversion.javaToJavaScript(error);
+ }
+ if (javaResult === undefined) {
+ // This only happens when the return type is void.
+ return undefined;
+ }
+ // If necessary, box the Java return value.
+ const retHub = metadata.returnHub;
+ javaResult = conversion.boxIfNeeded(javaResult, conversion.getHubKindOrdinal(retHub));
+ const javaScriptResult = conversion.javaToJavaScript(javaResult);
+ return javaScriptResult;
+ }
+ }
+ const methodName = name !== null ? "method '" + name + "'" : "single abstract method";
+ throw new Error("No matching signature for " + methodName + " and argument list '" + javaScriptArgs + "'");
+ }
+
+ /**
+ * The Java type hierarchy is not modelled in the proxy and the proxied
+ * object has no prototype.
+ */
+ getPrototypeOf(target) {
+ return null;
+ }
+
+ /**
+ * Modifying the prototype of the proxied object is not allowed.
+ */
+ setPrototypeOf(target, prototype) {
+ return false;
+ }
+
+ /**
+ * Proxied objects are not extensible in any way.
+ */
+ isExtensible(target) {
+ return false;
+ }
+
+ /**
+ * We allow calling Object.preventExtensions on the proxy.
+ * However, it won't do anything, the proxy handler already prevents extensions.
+ */
+ preventExtensions(target) {
+ return true;
+ }
+
+ getOwnPropertyDescriptor(target, key) {
+ const value = this._loadMethod(target, key);
+ if (value === undefined) {
+ return undefined;
+ }
+ return {
+ value: value,
+ writable: false,
+ enumerable: false,
+ configurable: false,
+ };
+ }
+
+ /**
+ * Defining properties on the Java object is not allowed.
+ */
+ defineProperty(target, key, descriptor) {
+ return false;
+ }
+
+ has(target, key) {
+ return this._loadMethod(target, key) !== undefined;
+ }
+
+ get(target, key) {
+ if (key === runtime.symbol.javaNative) {
+ return target(runtime.symbol.javaNative);
+ } else {
+ const javaObject = target(runtime.symbol.javaNative);
+ // TODO GR-60603 Deal with arrays in WasmGC backend
+ if (Array.isArray(javaObject) && typeof key === "string") {
+ const index = Number(key);
+ if (0 <= index && index < javaObject.length) {
+ return conversion.javaToJavaScript(javaObject[key]);
+ } else if (key === "length") {
+ return javaObject.length;
+ }
+ }
+ }
+ return this._loadMethod(target, key);
+ }
+
+ set(target, key, value, receiver) {
+ const javaObject = target(runtime.symbol.javaNative);
+ // TODO GR-60603 Deal with arrays in WasmGC backend
+ if (Array.isArray(javaObject)) {
+ const index = Number(key);
+ if (0 <= index && index < javaObject.length) {
+ javaObject[key] = conversion.javaScriptToJava(value);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deleting properties on the Java object is not allowed.
+ */
+ deleteProperty(target, key) {
+ return false;
+ }
+
+ ownKeys(target) {
+ return this._methodNames();
+ }
+
+ apply(target, javaScriptThisArg, javaScriptArgs) {
+ // We need to convert the Proxy's target function to the Java Proxy.
+ const javaScriptJavaProxy = conversion.toProxy(target(runtime.symbol.javaNative));
+ // Note: the JavaScript this argument for the apply method is never exposed to Java, so we just ignore it.
+ return this._applyWithObject(javaScriptJavaProxy, javaScriptArgs);
+ }
+
+ _getSingleAbstractMethod(javaScriptJavaProxy) {
+ return this._getClassMetadata().singleAbstractMethod;
+ }
+
+ _applyWithObject(javaScriptJavaProxy, javaScriptArgs) {
+ const sam = this._getSingleAbstractMethod(javaScriptJavaProxy);
+ if (sam === undefined) {
+ throw new Error("Java Proxy is not a functional interface, so 'apply' cannot be called from JavaScript.");
+ }
+ return this._invokeProxyMethod(null, [sam], javaScriptJavaProxy, ...javaScriptArgs);
+ }
+
+ /**
+ * Create uninitialized instance of given Java type.
+ */
+ _createInstance(hub) {
+ throw new Error("Unimplemented: ProxyHandler._createInstance");
+ }
+
+ construct(target, argumentsList) {
+ const javaThis = target(runtime.symbol.javaNative);
+ // This is supposed to be a proxy handler for java.lang.Class objects
+ // and javaThis is supposed to be some Class instance.
+ if (!conversion.isJavaLangClass(javaThis)) {
+ throw new Error(
+ "Cannot invoke the 'new' operator. The 'new' operator can only be used on Java Proxies that represent the 'java.lang.Class' type."
+ );
+ }
+ // Allocate the Java object from Class instance
+ const javaInstance = this._createInstance(javaThis);
+ // Lookup constructor method of the target class.
+ // This proxy handler is for java.lang.Class while javaThis is a
+ // java.lang.Class instance for some object type for which we want to
+ // lookup the constructor.
+ const instanceProxyHandler = conversion.getOrCreateProxyHandler(conversion._getProxyHandlerArg(javaInstance));
+ const javaConstructorMethod = instanceProxyHandler._getJavaConstructorMethod();
+ // Convert the Java instance to JS (usually creates a proxy)
+ const javaScriptInstance = conversion.javaToJavaScript(javaInstance);
+ // Call the Java constructor method.
+ javaConstructorMethod(javaScriptInstance, ...argumentsList);
+ return javaScriptInstance;
+ }
+}
+
+
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * WasmGC-backend specific implementation of conversion code.
+ *
+ * In the WasmGC backend, all Java values are originally Wasm values, with
+ * objects being references to Wasm structs. How those values are represented
+ * in Java is governed by the "WebAssembly JavaScript Interface".
+ * Java Objects are represented as opaque JS objects, these objects do not have
+ * any properties of their own nor are they extensible (though identity with
+ * regards to the === operator is preserved), only when passing them back to
+ * Wasm code can they be manipulated.
+ * Java long values are represented as JS BigInt values.
+ *
+ * Unlike the JS backend, Java code compiled to WasmGC cannot directly be passed
+ * JS objects as arguments due to Wasm's type safety. Instead, JS values are
+ * first wrapped in WasmExtern, a custom Java class with some special handling
+ * to have it store and externref value in one of its fields.
+ *
+ * To support JSValue instances, there are Java factory methods for each
+ * subclass exported under convert.create.*, which create a new instance of the
+ * type and associate it with the given JavaScript value.
+ * Instead of having the JS code store the JS native value directly in the
+ * WasmGC object (which won't work because they are immutable on the JS side),
+ * the JS value is first wrapped in a WasmExtern, and then passed to Java, where
+ * that WasmExtern value is stored in a hidden field of the JSValue instance.
+ */
+class WasmGCConversion extends Conversion {
+ constructor() {
+ super();
+ this.proxyHandlers = new WeakMap();
+ }
+
+ #wrapExtern(jsObj) {
+ return getExport("extern.wrap")(jsObj);
+ }
+
+ #unwrapExtern(javaObj) {
+ return getExport("extern.unwrap")(javaObj);
+ }
+
+ handleJSError(jsError) {
+ if (jsError instanceof WebAssembly.Exception) {
+ // Wasm exceptions can be rethrown as-is. They will be caught in Wasm code
+ throw jsError;
+ } else {
+ // Have Java code wrap the JS error in a Java JSError instance and throw it.
+ getExport("convert.throwjserror")(this.javaScriptToJava(jsError));
+ }
+ }
+
+ extractJavaScriptNumber(jldouble) {
+ return getExport("unbox.double")(jldouble);
+ }
+
+ extractJavaScriptString(jlstring) {
+ return charArrayToString(proxyCharArray(getExport("string.tochars")(jlstring)));
+ }
+
+ extractJavaScriptArray(jarray) {
+ const length = getExport("array.length")(jarray);
+ const jsarray = new Array(length);
+ for (let i = 0; i < length; i++) {
+ jsarray[i] = this.javaToJavaScript(getExport("array.object.read")(jarray, i));
+ }
+ return jsarray;
+ }
+
+ createJavaBoolean(x) {
+ return getExport("box.boolean")(x);
+ }
+
+ createJavaByte(x) {
+ return getExport("box.byte")(x);
+ }
+
+ createJavaShort(x) {
+ return getExport("box.short")(x);
+ }
+
+ createJavaCharacter(x) {
+ return getExport("box.char")(x);
+ }
+
+ createJavaInteger(x) {
+ return getExport("box.int")(x);
+ }
+
+ createJavaFloat(x) {
+ return getExport("box.float")(x);
+ }
+
+ createJavaLong(x) {
+ return getExport("box.long")(x);
+ }
+
+ createJavaDouble(x) {
+ return getExport("box.double")(x);
+ }
+
+ getHubKindOrdinal(hub) {
+ return getExport("class.getkindordinal")(hub);
+ }
+
+ getBoxedHub(jlClass) {
+ return getExport("class.getboxedhub")(jlClass);
+ }
+
+ unboxBoolean(jlBoolean) {
+ return getExport("unbox.boolean")(jlBoolean);
+ }
+
+ unboxByte(jlByte) {
+ return getExport("unbox.byte")(jlByte);
+ }
+
+ unboxShort(jlShort) {
+ return getExport("unbox.short")(jlShort);
+ }
+
+ unboxChar(jlChar) {
+ return getExport("unbox.char")(jlChar);
+ }
+
+ unboxInt(jlInt) {
+ return getExport("unbox.int")(jlInt);
+ }
+
+ unboxFloat(jlFloat) {
+ return getExport("unbox.float")(jlFloat);
+ }
+
+ unboxLong(jlLong) {
+ return getExport("unbox.long")(jlLong);
+ }
+
+ unboxDouble(jlDouble) {
+ return getExport("unbox.double")(jlDouble);
+ }
+
+ createJSUndefined() {
+ return getExport("convert.create.jsundefined")();
+ }
+
+ createJSBoolean(boolean) {
+ return getExport("convert.create.jsboolean")(this.#wrapExtern(boolean));
+ }
+
+ createJSNumber(number) {
+ return getExport("convert.create.jsnumber")(this.#wrapExtern(number));
+ }
+
+ createJSBigInt(bigint) {
+ return getExport("convert.create.jsbigint")(this.#wrapExtern(bigint));
+ }
+
+ createJSString(string) {
+ return getExport("convert.create.jsstring")(this.#wrapExtern(string));
+ }
+
+ createJSSymbol(symbol) {
+ return getExport("convert.create.jssymbol")(this.#wrapExtern(symbol));
+ }
+
+ createJSObject(obj) {
+ return getExport("convert.create.jsobject")(this.#wrapExtern(obj));
+ }
+
+ isInternalJavaObject(obj) {
+ return getExport("extern.isjavaobject")(obj);
+ }
+
+ isPrimitiveHub(hub) {
+ return getExport("class.isprimitive")(hub);
+ }
+
+ isJavaLangString(obj) {
+ return getExport("convert.isjavalangstring")(obj);
+ }
+
+ isJavaLangClass(obj) {
+ return getExport("convert.isjavalangclass")(obj);
+ }
+
+ isInstance(obj, hub) {
+ return getExport("object.isinstance")(obj, hub);
+ }
+
+ getOrCreateProxyHandler(clazz) {
+ if (!this.proxyHandlers.has(clazz)) {
+ this.proxyHandlers.set(clazz, new WasmGCProxyHandler(clazz));
+ }
+ return this.proxyHandlers.get(clazz);
+ }
+
+ _getProxyHandlerArg(obj) {
+ return getExport("object.getclass")(obj);
+ }
+
+ javaToJavaScript(x) {
+ let effectiveJavaObject = x;
+
+ /*
+ * When catching exceptions in JavaScript, exceptions thrown from Java
+ * aren't caught as Java objects, but as WebAssembly.Exception objects.
+ * Instead of having to do special handling whenever we catch an
+ * exception in JS, converting to JavaScript first unwraps the original
+ * Java Throwable before converting.
+ */
+ if (x instanceof WebAssembly.Exception && x.is(getExport("tag.throwable"))) {
+ effectiveJavaObject = x.getArg(getExport("tag.throwable"), 0);
+ }
+
+ return this.#unwrapExtern(getExport("convert.javatojavascript")(effectiveJavaObject));
+ }
+
+ throwClassCastExceptionImpl(javaObject, tpeNameJavaString) {
+ getExport("convert.throwClassCastException")(javaObject, tpeNameJavaString);
+ }
+
+ coerceJavaProxyToJavaScriptType(proxyHandler, proxy, tpe) {
+ const o = proxy[runtime.symbol.javaNative];
+ switch (tpe) {
+ case "boolean":
+ // Due to Java booleans being numbers, the double-negation is necessary.
+ return !!this.#unwrapExtern(getExport("convert.coerce.boolean")(o));
+ case "number":
+ return this.#unwrapExtern(getExport("convert.coerce.number")(o));
+ case "bigint":
+ const bs = this.#unwrapExtern(getExport("convert.coerce.bigint")(o));
+ return BigInt(bs);
+ case "string":
+ return this.#unwrapExtern(getExport("convert.coerce.string")(o));
+ case "object":
+ return this.#unwrapExtern(getExport("convert.coerce.object")(o));
+ case "function":
+ const sam = proxyHandler._getSingleAbstractMethod(proxy);
+ if (sam !== undefined) {
+ return (...args) => proxyHandler._applyWithObject(proxy, args);
+ }
+ this.throwClassCastException(o, tpe);
+ case Uint8Array:
+ case Int8Array:
+ case Uint16Array:
+ case Int16Array:
+ case Int32Array:
+ case Float32Array:
+ case BigInt64Array:
+ case Float64Array:
+ // TODO GR-60603 Support array coercion
+ throw new Error("Coercion to arrays is not supported yet");
+ default:
+ this.throwClassCastException(o, tpe);
+ }
+ }
+}
+
+const METADATA_PREFIX = "META.";
+const SAM_PREFIX = "SAM.";
+const METADATA_SEPARATOR = " ";
+
+class WasmGCProxyHandler extends ProxyHandler {
+ #classMetadata = null;
+
+ constructor(clazz) {
+ super();
+ this.clazz = clazz;
+ }
+
+ #lookupClass(name) {
+ const clazz = getExport("conversion.classfromencoding")(toJavaString(name));
+ if (!clazz) {
+ throw new Error("Failed to lookup class " + name);
+ }
+
+ return clazz;
+ }
+
+ _getClassMetadata() {
+ if (!this.#classMetadata) {
+ this.#classMetadata = new ClassMetadata({}, this.#extractSingleAbstractMethod(), this.#createMethodTable());
+ }
+ return this.#classMetadata;
+ }
+
+ #decodeMetadata(exports, name, prefix) {
+ if (name.startsWith(prefix)) {
+ const parts = name.slice(prefix.length).split(METADATA_SEPARATOR);
+ if (parts.length < 3) {
+ throw new Error("Malformed metadata: " + name);
+ }
+ const classId = parts[0];
+
+ if (this.#lookupClass(classId) == this.clazz) {
+ const methodName = parts[1];
+ const returnTypeId = parts[2];
+ const argTypeIds = parts.slice(3);
+
+ return [
+ methodName,
+ mmeta(
+ exports[name],
+ this.#lookupClass(returnTypeId),
+ ...argTypeIds.map((i) => this.#lookupClass(i))
+ ),
+ ];
+ }
+ }
+
+ return undefined;
+ }
+
+ #extractSingleAbstractMethod() {
+ const exports = getExports();
+
+ for (const name in exports) {
+ const meta = this.#decodeMetadata(exports, name, SAM_PREFIX);
+ if (meta !== undefined) {
+ return meta[1];
+ }
+ }
+
+ return undefined;
+ }
+
+ #createMethodTable() {
+ const exports = getExports();
+ const methodTable = {};
+
+ for (const name in exports) {
+ const meta = this.#decodeMetadata(exports, name, METADATA_PREFIX);
+ if (meta !== undefined) {
+ let methodName = meta[0];
+
+ if (methodName === "<init>") {
+ methodName = runtime.symbol.ctor;
+ }
+
+ if (!methodTable.hasOwnProperty(methodName)) {
+ methodTable[methodName] = [];
+ }
+
+ methodTable[methodName].push(meta[1]);
+ }
+ }
+
+ return methodTable;
+ }
+
+ _getClassName() {
+ return conversion.extractJavaScriptString(getExport("class.getname")(this.clazz));
+ }
+
+ _linkMethodPrototype() {
+ // Link the prototype chain of the superclass' proxy handler, to include super methods.
+ if (!getExport("class.isjavalangobject")(this.clazz)) {
+ const parentClass = getExport("class.superclass")(this.clazz);
+ const parentProxyHandler = conversion.getOrCreateProxyHandler(parentClass);
+ Object.setPrototypeOf(this._getMethods(), parentProxyHandler._getMethods());
+ }
+ }
+
+ _createInstance(hub) {
+ return getExport("unsafe.create")(hub);
+ }
+}
+
+const conversion = new WasmGCConversion();
+const createVM = function(vmArgs, data) {
+runtime.data = data;
+wasmRun(vmArgs);
+
+return vm;
+
+};
+GraalVM.Config = Config;
+/** @suppress {checkVars,duplicate} */ GraalVM.run = async function (vmArgs, config = new GraalVM.Config()) {
+ let data = new Data(config);
+ for (let libname in config.libraries) {
+ const content = await runtime.fetchText(config.libraries[libname]);
+ data.libraries[libname] = content;
+ }
+data.wasm = await wasmInstantiate(config, vmArgs);
+ let vm = createVM(vmArgs, data);
+ return vm;
+}
+})();
+
+})();
+
+})();
+
+(function() {
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Try to load commandline arguments for various JS runtimes.
+ */
+function load_cmd_args() {
+ if (typeof process === "object" && "argv" in process) {
+ // nodejs
+ return process.argv.slice(2);
+ } else if (typeof scriptArgs == "object") {
+ // spidermonkey
+ return scriptArgs;
+ }
+
+ return window.compilerArgs;
+ return [];
+}
+
+const config = new GraalVM.Config();
+GraalVM.run(load_cmd_args(),config).catch(console.error);
+});