diff --git a/.gitmodules b/.gitmodules index f9d644b313..ffbff9d74d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,4 +31,12 @@ path = third_party/madler/zlib url = git@github.com:madler/zlib.git ignore = dirty - + shallow = true +[submodule "graalvm"] + path = third_party/oracle/graalvm/graal + url = git@github.com:oracle/graal.git + shallow = true +[submodule "graaljs"] + path = third_party/oracle/graalvm/graaljs + url = git@github.com:elide-dev/graaljs.git + shallow = true diff --git a/packages/cli/build.gradle.kts b/packages/cli/build.gradle.kts index 75c2ec82c6..39bab3308d 100644 --- a/packages/cli/build.gradle.kts +++ b/packages/cli/build.gradle.kts @@ -1963,3 +1963,15 @@ listOf( } } } + +val (jsGroup, jsName) = libs.graalvm.js.language.get().let { + it.group to it.name +} +configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("${jsGroup}:${jsName}")).apply { + using(project(":packages:graalvm-js")) + because("Uses Elide's patched version of GraalJs") + } + } +} diff --git a/packages/engine/api/engine.api b/packages/engine/api/engine.api index 69827aaa73..6ecd57f698 100644 --- a/packages/engine/api/engine.api +++ b/packages/engine/api/engine.api @@ -366,6 +366,50 @@ public final class elide/runtime/gvm/cfg/LanguageDefaults { public final fun getDEFAULT_TIMEZONE ()Ljava/time/ZoneId; } +public abstract interface class elide/runtime/gvm/loader/ModuleFactory { + public abstract fun load (Lelide/runtime/gvm/loader/ModuleInfo;)Ljava/lang/Object; +} + +public final class elide/runtime/gvm/loader/ModuleInfo : java/lang/Record, java/lang/Comparable { + public static final field Companion Lelide/runtime/gvm/loader/ModuleInfo$Companion; + public synthetic fun (Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun compareTo (Lelide/runtime/gvm/loader/ModuleInfo;)I + public synthetic fun compareTo (Ljava/lang/Object;)I + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun dependencies ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public static final fun find (Ljava/lang/String;)Lelide/runtime/gvm/loader/ModuleInfo; + public fun hashCode ()I + public final fun name ()Ljava/lang/String; + public static final fun of (Ljava/lang/String;[Ljava/lang/String;)Lelide/runtime/gvm/loader/ModuleInfo; + public fun toString ()Ljava/lang/String; +} + +public final class elide/runtime/gvm/loader/ModuleInfo$Companion { + public final fun find (Ljava/lang/String;)Lelide/runtime/gvm/loader/ModuleInfo; + public final fun getAllModuleInfos ()Ljava/util/Map; + public final fun of (Ljava/lang/String;[Ljava/lang/String;)Lelide/runtime/gvm/loader/ModuleInfo; +} + +public abstract interface class elide/runtime/gvm/loader/ModuleRegistrar { + public abstract fun deferred (Lelide/runtime/gvm/loader/ModuleInfo;Lelide/runtime/gvm/loader/ModuleFactory;)V + public abstract fun register (Lelide/runtime/gvm/loader/ModuleInfo;Ljava/lang/Object;)V +} + +public final class elide/runtime/gvm/loader/ModuleRegistry : elide/runtime/gvm/loader/ModuleRegistrar, elide/runtime/gvm/loader/ModuleResolver { + public static final field INSTANCE Lelide/runtime/gvm/loader/ModuleRegistry; + public fun contains (Lelide/runtime/gvm/loader/ModuleInfo;)Z + public fun deferred (Lelide/runtime/gvm/loader/ModuleInfo;Lelide/runtime/gvm/loader/ModuleFactory;)V + public fun load (Lelide/runtime/gvm/loader/ModuleInfo;)Ljava/lang/Object; + public fun register (Lelide/runtime/gvm/loader/ModuleInfo;Ljava/lang/Object;)V +} + +public abstract interface class elide/runtime/gvm/loader/ModuleResolver { + public abstract fun contains (Lelide/runtime/gvm/loader/ModuleInfo;)Z + public abstract fun load (Lelide/runtime/gvm/loader/ModuleInfo;)Ljava/lang/Object; +} + public abstract class elide/runtime/plugins/AbstractLanguageConfig { public static final field Companion Lelide/runtime/plugins/AbstractLanguageConfig$Companion; public fun ()V @@ -467,21 +511,6 @@ public final class elide/runtime/plugins/AbstractLanguagePlugin$LanguagePluginMa public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public abstract class elide/runtime/plugins/api/NativePlugin : elide/runtime/plugins/api/NativePluginAPI { - protected fun (Ljava/lang/String;)V - protected fun apply ([Ljava/lang/String;)V - public fun context (Lorg/graalvm/polyglot/Engine;Lorg/graalvm/polyglot/Context$Builder;[Ljava/lang/String;)V - public fun getPluginId ()Ljava/lang/String; - public fun init ()V - public static fun initialize (Lelide/runtime/plugins/api/NativePlugin;)Lorg/graalvm/polyglot/Context$Builder; -} - -public abstract interface class elide/runtime/plugins/api/NativePluginAPI { - public abstract fun context (Lorg/graalvm/polyglot/Engine;Lorg/graalvm/polyglot/Context$Builder;[Ljava/lang/String;)V - public abstract fun getPluginId ()Ljava/lang/String; - public abstract fun init ()V -} - public final class elide/runtime/plugins/bindings/Bindings { public static final field Plugin Lelide/runtime/plugins/bindings/Bindings$Plugin; public synthetic fun (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/packages/engine/src/main/java/elide/runtime/plugins/api/NativePlugin.java b/packages/engine/src/main/java/elide/runtime/plugins/api/NativePlugin.java deleted file mode 100644 index 4d9b399964..0000000000 --- a/packages/engine/src/main/java/elide/runtime/plugins/api/NativePlugin.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2024 Elide Technologies, Inc. - * - * Licensed under the MIT license (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://opensource.org/license/mit/ - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ -package elide.runtime.plugins.api; - -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Engine; - -/** TBD. */ -public abstract class NativePlugin implements NativePluginAPI { - private final String id; - private static final Engine PLUGIN_ENGINE = - Engine.newBuilder().allowExperimentalOptions(true).build(); - - protected NativePlugin(String pluginId) { - this.id = pluginId; - } - - public String getPluginId() { - return id; - } - - public void context(Engine engine, Context.Builder builder, String[] args) { - // (nothing by default) - } - - @Override - public void init() { - // (nothing by default) - } - - protected void apply(String[] args) { - context(PLUGIN_ENGINE, initialize(this), args); - } - - public static Context.Builder initialize(NativePlugin plugin) { - var builder = - Context.newBuilder() - .allowExperimentalOptions(true) - .allowNativeAccess(true) - .engine(PLUGIN_ENGINE); - - plugin.context(PLUGIN_ENGINE, builder, new String[0]); - return builder; - } -} diff --git a/packages/graalvm-rb/src/main/java/elide/runtime/ruby/ElideRubyLanguage.java b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleFactory.kt similarity index 56% rename from packages/graalvm-rb/src/main/java/elide/runtime/ruby/ElideRubyLanguage.java rename to packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleFactory.kt index edfc784676..6bf6b486e9 100644 --- a/packages/graalvm-rb/src/main/java/elide/runtime/ruby/ElideRubyLanguage.java +++ b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleFactory.kt @@ -10,15 +10,23 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under the License. */ -package elide.runtime.ruby; +@file:OptIn(DelicateElideApi::class) -import elide.runtime.plugins.api.NativePlugin; +package elide.runtime.gvm.loader -/** TBD. */ -public class ElideRubyLanguage extends NativePlugin { - private static final String ELIDE_RUBY = "ruby"; +import elide.runtime.core.DelicateElideApi - ElideRubyLanguage() { - super(ELIDE_RUBY); - } +/** + * ## Module Factory + * + * A factory which creates an instance of a synthesized module; loaded from a [ModuleResolver]. + */ +public fun interface ModuleFactory { + /** + * Load a module from a [ModuleInfo]. + * + * @param module The module to load. + * @return The loaded module. + */ + public fun load(module: ModuleInfo): Any } diff --git a/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleInfo.kt b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleInfo.kt new file mode 100644 index 0000000000..c103139573 --- /dev/null +++ b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleInfo.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ +package elide.runtime.gvm.loader + +import java.util.concurrent.ConcurrentSkipListMap + +/** + * Assigned string name/ID for a code module. + */ +public typealias ModuleId = String + +/** + * ## Module Info + * + * Describes information about a code module of some kind; the module is addressed by a simple [name]. + * + * @property name The name of the module. + * @property dependencies The list of module names that this module depends on. + */ +@ConsistentCopyVisibility +@JvmRecord public data class ModuleInfo private constructor ( + public val name: ModuleId, + public val dependencies: List = emptyList(), +) : Comparable { + override fun compareTo(other: ModuleInfo): Int = name.compareTo(other.name) + + public companion object { + public val allModuleInfos: MutableMap = ConcurrentSkipListMap() + + // Register a module info record. + @JvmStatic private fun register(name: String, vararg deps: String): ModuleInfo { + assert(name !in allModuleInfos) { "Module $name already registered" } + return ModuleInfo( + name = name, + dependencies = deps.toList(), + ).also { + allModuleInfos[name] = it + } + } + + // Obtain a module info record, registering if needed. + @JvmStatic public fun of(name: String, vararg deps: String): ModuleInfo = allModuleInfos.computeIfAbsent(name) { + register( + name = name, + deps = deps, + ) + } + + // Obtain a module info record, or return `null` if not found. + @JvmStatic public fun find(name: String): ModuleInfo? = allModuleInfos[name] + } +} diff --git a/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistrar.kt b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistrar.kt new file mode 100644 index 0000000000..575d11d760 --- /dev/null +++ b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistrar.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ +package elide.runtime.gvm.loader + +/** + * ## Module Registry + */ +public interface ModuleRegistrar { + /** + * Register a module with the module registry. + * + * @param module The module to register. + * @param impl An instance of the registered module. + */ + public fun register(module: ModuleInfo, impl: Any) + + /** + * Register a module with the module registry. + * + * @param module The module to register. + * @param producer The factory to create the module. + */ + public fun deferred(module: ModuleInfo, producer: ModuleFactory) +} diff --git a/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistry.kt b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistry.kt new file mode 100644 index 0000000000..971d13ff1a --- /dev/null +++ b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleRegistry.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.gvm.loader + +/** + * ## Module Registry + */ +public object ModuleRegistry : ModuleRegistrar, ModuleResolver { + private val registered = sortedMapOf() + private val factories = sortedMapOf() + + override fun register(module: ModuleInfo, impl: Any) { + assert(module !in registered) { "Module already registered: $module" } + assert(module !in factories) { "Module already registered as factory: $module" } + factories[module] = ModuleFactory { _ -> impl } + } + + override fun deferred(module: ModuleInfo, producer: ModuleFactory) { + factories[module] = producer + } + + override operator fun contains(mod: ModuleInfo): Boolean = mod in factories + + override fun load(info: ModuleInfo): Any = when (info) { + in registered -> registered[info]!! + in factories -> factories[info]!!.let { fac -> + fac.load(info).also { + registered[info] = it + } + } + else -> error("Module not registered: $info") + } +} diff --git a/packages/engine/src/main/java/elide/runtime/plugins/api/NativePluginAPI.java b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleResolver.kt similarity index 57% rename from packages/engine/src/main/java/elide/runtime/plugins/api/NativePluginAPI.java rename to packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleResolver.kt index 69b857fe57..1debd58481 100644 --- a/packages/engine/src/main/java/elide/runtime/plugins/api/NativePluginAPI.java +++ b/packages/engine/src/main/kotlin/elide/runtime/gvm/loader/ModuleResolver.kt @@ -10,16 +10,22 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under the License. */ -package elide.runtime.plugins.api; +package elide.runtime.gvm.loader -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Engine; - -/** TBD. */ -public interface NativePluginAPI { - public String getPluginId(); - - public void context(Engine engine, Context.Builder builder, String[] args); +/** + * ## Module Resolver + */ +public interface ModuleResolver { + /** + * Resolve a module by request. + */ + public operator fun contains(mod: ModuleInfo): Boolean - public void init(); + /** + * Load a module from its [ModuleInfo]. + * + * @param info The module info. + * @return The module implementation; expected to be a polyglot value or proxy-type object. + */ + public fun load(info: ModuleInfo): Any } diff --git a/packages/engine/src/main/kotlin/elide/runtime/plugins/AbstractLanguageConfig.kt b/packages/engine/src/main/kotlin/elide/runtime/plugins/AbstractLanguageConfig.kt index 9590490102..b302e599ef 100644 --- a/packages/engine/src/main/kotlin/elide/runtime/plugins/AbstractLanguageConfig.kt +++ b/packages/engine/src/main/kotlin/elide/runtime/plugins/AbstractLanguageConfig.kt @@ -27,7 +27,7 @@ import elide.runtime.core.PolyglotContext */ @DelicateElideApi public abstract class AbstractLanguageConfig { public companion object { - private const val EXPERIMENTAL_SECURE_INTERNALS = true + private const val EXPERIMENTAL_SECURE_INTERNALS = false } /** Mutable counterpart to [intrinsicBindings]. */ diff --git a/packages/graalvm-js/api/graalvm-js.api b/packages/graalvm-js/api/graalvm-js.api new file mode 100644 index 0000000000..c74feb3a89 --- /dev/null +++ b/packages/graalvm-js/api/graalvm-js.api @@ -0,0 +1,144 @@ +public abstract interface class elide/runtime/lang/javascript/CommonJSModuleProvider { + public abstract fun provide ()Ljava/lang/Object; +} + +public abstract interface class elide/runtime/lang/javascript/CommonJSModuleResolver { + public abstract fun resolve (Lcom/oracle/truffle/js/runtime/JSRealm;Ljava/lang/String;)Lelide/runtime/lang/javascript/CommonJSModuleProvider; +} + +public final class elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry : java/util/function/Predicate { + public static final field INSTANCE Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry; + public static final fun register (Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegateFactory;)V + public static final fun resolve (Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest;Lcom/oracle/truffle/js/runtime/JSRealm;)Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader; + public fun test (Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest;)Z + public synthetic fun test (Ljava/lang/Object;)Z +} + +public abstract interface class elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegateFactory : java/util/function/Predicate { + public abstract fun invoke (Lcom/oracle/truffle/js/runtime/JSRealm;)Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader; +} + +public final class elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest : java/lang/Record { + public static final field Companion Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest$Companion; + public synthetic fun (Lcom/oracle/truffle/api/source/Source;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/oracle/truffle/api/source/Source; + public final fun component2 ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun label ()Ljava/lang/String; + public final fun source ()Lcom/oracle/truffle/api/source/Source; + public fun toString ()Ljava/lang/String; +} + +public final class elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest$Companion { + public final fun of (Lcom/oracle/truffle/api/source/Source;)Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest; + public final fun of (Ljava/lang/String;)Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest; +} + +public abstract interface class elide/runtime/lang/javascript/JSModuleProvider { + public abstract fun resolve (Lelide/runtime/gvm/loader/ModuleInfo;)Ljava/lang/Object; +} + +public class elide/runtime/lang/javascript/JSRealmPatcher { + public fun ()V + public static fun setModuleLoader (Lcom/oracle/truffle/js/runtime/JSRealm;Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader;)V +} + +public final class elide/runtime/lang/javascript/JavaScriptLang { + public static final field INSTANCE Lelide/runtime/lang/javascript/JavaScriptLang; + public final fun initialize ()V +} + +public final class elide/runtime/lang/javascript/NodeModuleName { + public static final field ASSERT Ljava/lang/String; + public static final field ASYNC_HOOKS Ljava/lang/String; + public static final field BUFFER Ljava/lang/String; + public static final field CHILD_PROCESS Ljava/lang/String; + public static final field CLUSTER Ljava/lang/String; + public static final field CONSOLE Ljava/lang/String; + public static final field CONSTANTS Ljava/lang/String; + public static final field CRYPTO Ljava/lang/String; + public static final field DGRAM Ljava/lang/String; + public static final field DNS Ljava/lang/String; + public static final field DOMAIN Ljava/lang/String; + public static final field EVENTS Ljava/lang/String; + public static final field FS Ljava/lang/String; + public static final field FS_PROMISES Ljava/lang/String; + public static final field HTTP Ljava/lang/String; + public static final field HTTP2 Ljava/lang/String; + public static final field HTTPS Ljava/lang/String; + public static final field INSPECTOR Ljava/lang/String; + public static final field INSTANCE Lelide/runtime/lang/javascript/NodeModuleName; + public static final field MODULE Ljava/lang/String; + public static final field NET Ljava/lang/String; + public static final field OS Ljava/lang/String; + public static final field PATH Ljava/lang/String; + public static final field PERF_HOOKS Ljava/lang/String; + public static final field PROCESS Ljava/lang/String; + public static final field PUNYCODE Ljava/lang/String; + public static final field QUERYSTRING Ljava/lang/String; + public static final field READLINE Ljava/lang/String; + public static final field REPL Ljava/lang/String; + public static final field STREAM Ljava/lang/String; + public static final field STRING_DECODER Ljava/lang/String; + public static final field TIMERS Ljava/lang/String; + public static final field TLS Ljava/lang/String; + public static final field TRACE_EVENTS Ljava/lang/String; + public static final field TTY Ljava/lang/String; + public static final field URL Ljava/lang/String; + public static final field UTIL Ljava/lang/String; + public static final field V8 Ljava/lang/String; + public static final field VM Ljava/lang/String; + public static final field WORKER_THREADS Ljava/lang/String; + public static final field ZLIB Ljava/lang/String; +} + +public abstract interface class elide/runtime/lang/javascript/SyntheticJSModule : elide/runtime/lang/javascript/CommonJSModuleProvider, elide/runtime/lang/javascript/JSModuleProvider { + public fun exports ()[Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public abstract fun provide ()Ljava/lang/Object; + public fun provider ()Lelide/runtime/lang/javascript/JSModuleProvider; + public fun resolve (Lelide/runtime/gvm/loader/ModuleInfo;)Ljava/lang/Object; +} + +public final class elide/runtime/lang/javascript/SyntheticJSModule$ExportKind : java/lang/Enum { + public static final field CLASS Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static final field DEFAULT Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static final field METHOD Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static final field PROPERTY Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static fun values ()[Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; +} + +public final class elide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol : java/lang/Record { + public static final field Companion Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion; + public fun (Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun cls (Ljava/lang/String;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static synthetic fun copy$default (Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol;Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static final fun default (Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public fun equals (Ljava/lang/Object;)Z + public final fun from ()Ljava/lang/String; + public fun hashCode ()I + public final fun kind ()Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind; + public static final fun method (Ljava/lang/String;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public final fun name ()Ljava/lang/String; + public static final fun of (Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public fun toString ()Ljava/lang/String; +} + +public final class elide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion { + public final fun cls (Ljava/lang/String;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static synthetic fun cls$default (Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public final fun default (Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static synthetic fun default$default (Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public final fun method (Ljava/lang/String;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static synthetic fun method$default (Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public final fun of (Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; + public static synthetic fun of$default (Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol$Companion;Ljava/lang/String;Lelide/runtime/lang/javascript/SyntheticJSModule$ExportKind;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/lang/javascript/SyntheticJSModule$ExportedSymbol; +} + diff --git a/packages/graalvm-js/build.gradle.kts b/packages/graalvm-js/build.gradle.kts new file mode 100644 index 0000000000..a20bd1d8e5 --- /dev/null +++ b/packages/graalvm-js/build.gradle.kts @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +import elide.internal.conventions.kotlin.KotlinTarget +import elide.internal.conventions.publishing.publish + +plugins { + alias(libs.plugins.micronaut.graalvm) + + kotlin("jvm") + kotlin("kapt") + kotlin("plugin.allopen") + + alias(libs.plugins.elide.conventions) +} + +elide { + publishing { + id = "graalvm-js" + name = "Elide JS integration package for GraalVM" + description = "Integration package with GraalVM, Elide, and JS." + + publish("jvm") { + from(components["kotlin"]) + } + } + + kotlin { + target = KotlinTarget.JVM + explicitApi = true + } + + checks { + // Broken horribly. + spotless = false + } +} + +val gvmJarsRoot = rootProject.layout.projectDirectory.dir("third_party/oracle") + +val patchedLibs = files( + gvmJarsRoot.file("graaljs.jar"), +) + +val patchedDependencies: Configuration by configurations.creating { + isCanBeResolved = true +} + +dependencies { + api(projects.packages.engine) + api(patchedLibs) + api(libs.graalvm.truffle.api) + patchedDependencies(patchedLibs) +} + +configurations.all { + libs.graalvm.js.language.get().let { + exclude(group = it.group, module = it.name) + } +} diff --git a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/JSRealmPatcher.java b/packages/graalvm-js/src/main/java/elide/runtime/lang/javascript/JSRealmPatcher.java similarity index 73% rename from packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/JSRealmPatcher.java rename to packages/graalvm-js/src/main/java/elide/runtime/lang/javascript/JSRealmPatcher.java index 77c7d9fa79..2e9c3717c0 100644 --- a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/JSRealmPatcher.java +++ b/packages/graalvm-js/src/main/java/elide/runtime/lang/javascript/JSRealmPatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -10,18 +10,21 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under the License. */ -package elide.runtime.lang.typescript; +package elide.runtime.lang.javascript; import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.objects.JSModuleLoader; import java.lang.reflect.Field; -class JSRealmPatcher { - public static void setTSModuleLoader(JSRealm jsRealm, TypeScriptModuleLoader newModuleLoader) { +@Deprecated +public class JSRealmPatcher { + @Deprecated + public static void setModuleLoader(JSRealm jsRealm, JSModuleLoader newModuleLoader) { try { Field moduleLoaderField = JSRealm.class.getDeclaredField("moduleLoader"); moduleLoaderField.setAccessible(true); Object moduleLoader = moduleLoaderField.get(jsRealm); - if (!(moduleLoader instanceof TypeScriptModuleLoader)) { + if (moduleLoader != newModuleLoader) { moduleLoaderField.set(jsRealm, newModuleLoader); } } catch (NoSuchFieldException | IllegalAccessException e) { diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleProvider.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleProvider.kt new file mode 100644 index 0000000000..8030522bd0 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import com.oracle.truffle.api.TruffleFile + +/** + * ## Common JS Module Provider + * + * Augments the [JSModuleProvider] interface with support infrastructure for synthetic CommonJS imports; in this case, + * modules are rendered to strings, which are loaded through a synthetic [TruffleFile]. This is useful for making + * built-in modules accessible via a single and uniform interface through both ESM and CJS. + * + * When importing an ESM module, the [ElideJsModuleRouter] will call in to pre-render the Truffle file and inject it + * into the module cache. This allows ESM and CJS to behave interchangeably: it should not matter which module system + * is used first to load a built-in, as the result will be the same (a generated Truffle file is loaded for CJS and + * injected into the cache, and, as applicable, a synthetic module is returned directly for use in ESM contexts). + */ +public interface CommonJSModuleProvider { + /** + * Resolve this module to an implementation object which is usable in a guest context; this can also be a Truffle + * compatible file. + * + * @return Optional implementation object. + */ + public fun provide(): T & Any +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleResolver.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleResolver.kt new file mode 100644 index 0000000000..92308cd35c --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/CommonJSModuleResolver.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import com.oracle.truffle.js.runtime.JSRealm + +/** + * ## Common JS Module Resolver + * + */ +@FunctionalInterface +public fun interface CommonJSModuleResolver { + /** + * Resolve a requested CommonJS require to a module provider, which is capable of initializing and returning the + * module implementation; if `null` is returned, we are opting-out of built-in resolution. + * + * @return Provider or `null`. + */ + public fun resolve(realm: JSRealm, moduleIdentifier: String): CommonJSModuleProvider<*>? +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry.kt new file mode 100644 index 0000000000..25583ee803 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024-2025 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import com.oracle.truffle.api.source.Source +import com.oracle.truffle.js.runtime.JSRealm +import com.oracle.truffle.js.runtime.objects.JSModuleLoader +import java.util.function.Predicate +import elide.runtime.lang.javascript.DelegatedModuleLoaderRegistry.DelegatedModuleRequest + +/** + * ## Delegated Module Loader Registry + * + * Registry for module loaders which are used as "delegates" during the module loading process; such delegates filter + * based on the requested module names or paths. + */ +public object DelegatedModuleLoaderRegistry : Predicate { + /** + * Represents a request for a delegated JavaScript module load. + * + * @property source The source requesting the import. + * @property label The label of the request (e.g. the module name). + */ + @JvmRecord + @ConsistentCopyVisibility + public data class DelegatedModuleRequest private constructor ( + val source: Source?, + val label: String, + ) { + public companion object { + public fun of(source: Source): DelegatedModuleRequest = DelegatedModuleRequest(source, source.name) + public fun of(name: String): DelegatedModuleRequest = DelegatedModuleRequest(null, name) + } + } + + /** + * ### Delegate Factory + * + * Utility interface which functions both as a [Predicate] and a factory for creating a delegated module loader. + */ + public interface DelegateFactory : Predicate { + /** + * Invoke this factory to procure or otherwise obtain the [JSModuleLoader]. + * + * @param realm JavaScript realm for this loader. + * @return The module loader for the provided realm. + */ + public operator fun invoke(realm: JSRealm): JSModuleLoader + } + + // Registry of all delegate factories. + @JvmStatic private val registry: MutableList = mutableListOf() + + // Ask delegate factories if they accept this. + override fun test(t: DelegatedModuleRequest): Boolean = registry.any { it.test(t) } + + /** + * Register a new delegate factory. + * + * @param factory The factory to register. + */ + @JvmStatic public fun register(factory: DelegateFactory) { + registry.add(factory) + } + + /** + * Resolve a module request to a module loader. + * + * @param request The request to resolve. + * @return The module loader, if any. + */ + @JvmStatic public fun resolve(request: DelegatedModuleRequest, realm: JSRealm): JSModuleLoader { + return requireNotNull(registry.firstOrNull { it.test(request) }?.invoke(realm)) { + "Expected a module loader to resolve the request, but none was found." + } + } +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideJsModuleRouter.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideJsModuleRouter.kt new file mode 100644 index 0000000000..e8d62aba34 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideJsModuleRouter.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024-2025 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import com.oracle.truffle.api.TruffleFile +import com.oracle.truffle.js.runtime.CommonJSResolverHook +import com.oracle.truffle.js.runtime.JSEngine +import com.oracle.truffle.js.runtime.JSModuleLoaderFactory +import com.oracle.truffle.js.runtime.JSRealm +import com.oracle.truffle.js.runtime.objects.JSModuleLoader +import java.util.Optional +import kotlinx.atomicfu.atomic +import elide.runtime.Logging + +// Whether to install a custom JS module loader; should normally be `true`. +private const val ELIDE_LOADER_ENABLED = true + +/** + * ## Elide JavaScript Module Router + * + * This is the central installation point for JavaScript module injection; ESM and CJS are supported transparently. The + * module router must be installed before any JS code is executed (specifically, before evaluation calls), through the + * [install] method. + * + * Once installed, the JavaScript module router accepts requests from GraalJs to: + * + * - Create ESM module loader implementation instances on-demand, and + * - Intercept CommonJS module resolution requests. + * + * Based on registered built-ins, registered resolvers/module loaders, and so on, the router will then delegate to Elide + * to attempt to load the module. If the module: + * + * - Is not a built-in module, or + * - Cannot be located + * + * Then the module injection system will fall-back to default GraalJs behavior, through an NPM-compatible ESM loader. + * + * @see ElideUniversalJsModuleLoader Elide's universal module loader + * @see SyntheticJSModule Interface that modules are expected to implement to be eligible for injection + * @see JSModuleProvider Interface for ESM compatibility + * @see CommonJSResolverHook Interface for CJS compatibility + */ +internal object ElideJsModuleRouter : JSModuleLoaderFactory, CommonJSResolverHook { + private val initialized = atomic(false) + + /** + * Install the Elide JS module router hooks into the GraalJs runtime. + * + * This method should be called once, early in the application lifecycle, to ensure that Elide's module loader is + * available before any JavaScript code is executed. Installation only needs to take place once because these factory + * methods are installed statically. + * + * As a result, after calling this method, signals will be sent to Elide to load modules for the remainder of the + * application's lifecycle (unless cleared at a future time). + * + * This method is idempotent; repeated calls to this method are ignored. + */ + @JvmStatic fun install() { + if (!initialized.getAndSet(true)) { + JSEngine.setModuleLoaderFactory(this) + JSEngine.setCjsResolverHook(this) + } + } + + // Create an ESM-compatible loader for a new realm which is loading modules. Called on-demand. + override fun createLoader(realm: JSRealm): JSModuleLoader? = if (!ELIDE_LOADER_ENABLED) { + null + } else { + ElideUniversalJsModuleLoader.create( + realm, + ) + } + + // Resolve a CommonJS module require encountered in JavaScript. Called on-demand. + override fun resolveModule(realm: JSRealm, moduleIdentifier: String, entryPath: TruffleFile): Any? { + Logging.root().warn("CJS: require('$moduleIdentifier')") + return ElideUniversalJsModuleLoader.resolve(realm, moduleIdentifier) + ?.provide() + ?.let { realm.env.asGuestValue(it) } + } +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideUniversalJsModuleLoader.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideUniversalJsModuleLoader.kt new file mode 100644 index 0000000000..9af2ff3166 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/ElideUniversalJsModuleLoader.kt @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2024-2025 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +@file:Suppress( + "TOO_LONG_FUNCTION", + "ForbiddenComment", + "UnusedParameter", + "UnusedPrivateProperty", +) + +package elide.runtime.lang.javascript + +import com.oracle.js.parser.ir.Module +import com.oracle.js.parser.ir.Module.ExportEntry +import com.oracle.js.parser.ir.Module.ModuleRequest +import com.oracle.truffle.api.CallTarget +import com.oracle.truffle.api.frame.VirtualFrame +import com.oracle.truffle.api.source.Source +import com.oracle.truffle.js.builtins.commonjs.NpmCompatibleESModuleLoader +import com.oracle.truffle.js.nodes.JSFrameDescriptor +import com.oracle.truffle.js.nodes.JSFrameSlot +import com.oracle.truffle.js.runtime.JSArguments +import com.oracle.truffle.js.runtime.JSRealm +import com.oracle.truffle.js.runtime.JavaScriptRootNode +import com.oracle.truffle.js.runtime.Strings +import com.oracle.truffle.js.runtime.builtins.JSFunctionData +import com.oracle.truffle.js.runtime.objects.* +import com.oracle.truffle.js.runtime.objects.JSModuleRecord.Status +import org.graalvm.polyglot.proxy.ProxyObject +import java.io.File +import java.util.* +import java.util.concurrent.ConcurrentSkipListMap +import elide.core.api.Symbolic +import elide.runtime.Logging +import elide.runtime.gvm.loader.ModuleInfo +import elide.runtime.gvm.loader.ModuleRegistry +import elide.runtime.lang.javascript.DelegatedModuleLoaderRegistry.DelegatedModuleRequest +import elide.runtime.lang.javascript.ElideUniversalJsModuleLoader.ModuleStrategy.* + +// Whether assertions are enabled for comparison with regular module imports. +private const val COMPARE_WITH_BASE = false + +// Whether to disable this loader and apply default behavior. +private const val ALWAYS_FALLBACK = false + +private const val DENO_MODULE_PREFIX = "deno" +private const val BUN_MODULE_PREFIX = "bun" +private const val NODE_MODULE_PREFIX = "node" +private const val ELIDE_MODULE_PREFIX = "elide" +private const val ELIDE_TS_LANGUAGE_ID = "ts" + +// All built-in Elide modules. +private val allElideModules = sortedSetOf( + "sqlite", +) + +// Module prefixes which trigger some kind of behavior. +private val specialModulePrefixes = sortedSetOf( + DENO_MODULE_PREFIX, + BUN_MODULE_PREFIX, + NODE_MODULE_PREFIX, + ELIDE_MODULE_PREFIX, +) + +// All built-in Node modules in a sorted set. +private val allNodeModules = sortedSetOf( + NodeModuleName.ASSERT, + NodeModuleName.ASYNC_HOOKS, + NodeModuleName.BUFFER, + NodeModuleName.CHILD_PROCESS, + NodeModuleName.CLUSTER, + NodeModuleName.CONSOLE, + NodeModuleName.CONSTANTS, + NodeModuleName.CRYPTO, + NodeModuleName.DGRAM, + NodeModuleName.DNS, + NodeModuleName.DOMAIN, + NodeModuleName.EVENTS, + NodeModuleName.FS, + NodeModuleName.HTTP, + NodeModuleName.HTTP2, + NodeModuleName.HTTPS, + NodeModuleName.INSPECTOR, + NodeModuleName.MODULE, + NodeModuleName.NET, + NodeModuleName.OS, + NodeModuleName.PATH, + NodeModuleName.PERF_HOOKS, + NodeModuleName.PROCESS, + NodeModuleName.PUNYCODE, + NodeModuleName.TRACE_EVENTS, + NodeModuleName.QUERYSTRING, + NodeModuleName.READLINE, + NodeModuleName.REPL, + NodeModuleName.STREAM, + NodeModuleName.STRING_DECODER, + NodeModuleName.TIMERS, + NodeModuleName.TLS, + NodeModuleName.TTY, + NodeModuleName.URL, + NodeModuleName.UTIL, + NodeModuleName.V8, + NodeModuleName.VM, + NodeModuleName.WORKER_THREADS, + NodeModuleName.ZLIB, +) + +// All built-in Node modules. +@Suppress("VARIABLE_NAME_INCORRECT") public object NodeModuleName { + public const val ASSERT: String = "assert" + public const val ASYNC_HOOKS: String = "async_hooks" + public const val BUFFER: String = "buffer" + public const val CHILD_PROCESS: String = "child_process" + public const val CLUSTER: String = "cluster" + public const val CONSOLE: String = "console" + public const val CONSTANTS: String = "constants" + public const val CRYPTO: String = "crypto" + public const val DGRAM: String = "dgram" + public const val DNS: String = "dns" + public const val DOMAIN: String = "domain" + public const val EVENTS: String = "events" + public const val FS: String = "fs" + public const val FS_PROMISES: String = "fs/promises" + public const val HTTP: String = "http" + public const val HTTP2: String = "http2" + public const val HTTPS: String = "https" + public const val INSPECTOR: String = "inspector" + public const val MODULE: String = "module" + public const val NET: String = "net" + public const val OS: String = "os" + public const val PATH: String = "path" + public const val PERF_HOOKS: String = "perf_hooks" + public const val PROCESS: String = "process" + public const val PUNYCODE: String = "punycode" + public const val QUERYSTRING: String = "querystring" + public const val READLINE: String = "readline" + public const val REPL: String = "repl" + public const val STREAM: String = "stream" + public const val STRING_DECODER: String = "string_decoder" + public const val TIMERS: String = "timers" + public const val TLS: String = "tls" + public const val TRACE_EVENTS: String = "trace_events" + public const val TTY: String = "tty" + public const val URL: String = "url" + public const val UTIL: String = "util" + public const val V8: String = "v8" + public const val VM: String = "vm" + public const val WORKER_THREADS: String = "worker_threads" + public const val ZLIB: String = "zlib" +} + +// Implements Elide's internal ECMA-compliant module loader. +internal class ElideUniversalJsModuleLoader private constructor(realm: JSRealm) : NpmCompatibleESModuleLoader(realm) { + // Cache of injected modules. + private val injectedModuleCache: MutableMap = ConcurrentSkipListMap() + + // Synthesize an injected module. + private fun synthesizeInjected( + referencingModule: ScriptOrModule, + moduleRequest: ModuleRequest, + prefix: String?, + name: String, + ): JSModuleRecord { + if (name in moduleMap) { + return requireNotNull(moduleMap[name]) + } + return when (val info = ModuleInfo.find(name)) { + // not a registered module + null -> super.resolveImportedModule(referencingModule, moduleRequest) + + // present within module impl registry? + in ModuleRegistry -> { + val surface = requireNotNull(ModuleRegistry.load(info)) { "No such synthesized module: $info" } + val defaultExportName = Strings.DEFAULT + val defaultExport = ExportEntry.exportSpecifier(defaultExportName) + + @Suppress("UNCHECKED_CAST") + val propNames = when (surface) { + is ProxyObject -> surface.memberKeys as Array + else -> emptyArray() + }.map { + it to Strings.constant(it) + } + val propExports = propNames.map { (_, const) -> + ExportEntry.exportSpecifier(const) + } + + val frameDescBuilder = JSFrameDescriptor(Undefined.instance) + val defaultExportSlot = frameDescBuilder.addFrameSlot(defaultExportName) + val mappedSlots = sortedMapOf() + val exportEntries = LinkedList() + for ((slotName, const) in propNames) { + val export = ExportEntry.exportSpecifier(const) + exportEntries.add(export) + val slot = frameDescBuilder.addFrameSlot(const) + mappedSlots[slotName] = slot + } + + val frameDescriptor = frameDescBuilder.toFrameDescriptor() + + val localExportEntries = buildList { + add(defaultExport) + addAll(propExports) + } + + val modRecord = Module( + /* requestedModules = */ + emptyList(), + /* importEntries = */ + emptyList(), + /* localExportEntries = */ + localExportEntries, + /* indirectExportEntries = */ + emptyList(), + /* starExportEntries = */ + emptyList(), + /* imports = */ + null, + /* exports = */ + null, + ) + val source = Source.newBuilder("js", "", "$name.mjs") + .internal(true) + .cached(false) + .build() + + val rootNode: JavaScriptRootNode = object : JavaScriptRootNode( + realm.context.language, + source.createUnavailableSection(), + frameDescriptor, + ) { + override fun execute(frame: VirtualFrame): Any { + val module = JSArguments.getUserArgument(frame.arguments, 0) as JSModuleRecord + + module.environment?.let { + assert(module.status == Status.Evaluating) + setSyntheticModuleExport(module) + } ?: run { + assert(module.status == Status.Linking) + module.environment = frame.materialize() + } + return Undefined.instance + } + + private fun setSyntheticModuleExport(module: JSModuleRecord) { + module.environment.setObject(defaultExportSlot.index, surface) + when (surface) { + is ProxyObject -> { + for ((slotName, slot) in mappedSlots) { + // module.namespace.X + val member = surface.getMember(slotName) + module.environment.setObject(slot.index, realm.env.asGuestValue(member)) + } + } + + else -> error("Object is usable synthetic module: $surface") + } + } + } + val callTarget: CallTarget = rootNode.callTarget + val functionData = JSFunctionData.create( + realm.context, + callTarget, + callTarget, + 0, + Strings.EMPTY_STRING, + false, + false, + true, + true, + ) + val data = JSModuleData( + modRecord, + source, + functionData, + frameDescriptor, + ) + if (COMPARE_WITH_BASE) { + val base = super.resolveImportedModule(referencingModule, moduleRequest) + when { + base.module.localExportEntries.size != modRecord.localExportEntries.size -> error( + "Mismatch between expected export count ('${base.module.localExportEntries.size}') and " + + "actual export count ('${modRecord.localExportEntries.size}') for module '$name'", + ) + base.module.imports.size != modRecord.imports.size -> error( + "Mismatch between expected import count ('${base.module.imports.size}') and " + + "actual import count ('${modRecord.imports.size}') for module '$name'", + ) + base.module.indirectExportEntries.size != modRecord.indirectExportEntries.size -> error( + "Mismatch between expected indirect export count ('${base.module.indirectExportEntries.size}') and " + + "actual indirect export count ('${modRecord.indirectExportEntries.size}') for module '$name'", + ) + base.module.starExportEntries.size != modRecord.starExportEntries.size -> error( + "Mismatch between expected star export count ('${base.module.starExportEntries.size}') and " + + "actual star export count ('${modRecord.starExportEntries.size}') for module '$name'", + ) + base.moduleData.frameDescriptor.numberOfSlots != data.frameDescriptor.numberOfSlots -> error( + "Mismatch between expected slot count ('${base.moduleData.frameDescriptor.numberOfSlots}') and " + + "actual slot count ('${data.frameDescriptor.numberOfSlots}') for module '$name'", + ) + base.moduleData.frameDescriptor.numberOfAuxiliarySlots + != data.frameDescriptor.numberOfAuxiliarySlots -> error( + "Mismatch between expected auxiliary slot count " + + "('${base.moduleData.frameDescriptor.numberOfAuxiliarySlots}') and " + + "actual auxiliary slot count ('${data.frameDescriptor.numberOfAuxiliarySlots}') for module '$name'", + ) + } + } + ElideSyntheticModuleRecord( + prefix?.let { ModuleQualifier.resolve(it) } ?: ModuleQualifier.ELIDE, + data, + surface, + ) + } + + // registered but not implemented somehow; warn and fall back + else -> { + Logging.root().warn("No such synthesized module: '$name'. Falling back to VFS.") + super.resolveImportedModule(referencingModule, moduleRequest) + } + } + } + + // Load a module which needs pre-compilation (or some other special load step). + private fun loadDelegatedSourceModule(moduleSource: Source, moduleData: JSModuleData): JSModuleRecord { + val req = DelegatedModuleRequest.of(moduleSource) + return when (DelegatedModuleLoaderRegistry.test(req)) { + // allow the delegated registry to load the module + true -> DelegatedModuleLoaderRegistry.resolve(req, realm).loadModule(moduleSource, moduleData).let { + when (it) { + // the loader returned `null`, opting out of resolution; continue with defaults + null -> super.loadModule(moduleSource, moduleData) + + // we're good to return from here + else -> it + } + } + + // the delegated registry has indicated there is no matching loader for this + false -> super.loadModule(moduleSource, moduleData) + } + } + + // Load a module which needs pre-compilation (or some other special load step). + private fun resolveDelegatedImportedModule(ref: ScriptOrModule, req: ModuleRequest, name: String): JSModuleRecord { + val delegatedReq = DelegatedModuleRequest.of(name) + return when (DelegatedModuleLoaderRegistry.test(delegatedReq)) { + // allow the delegated registry to load the module + true -> DelegatedModuleLoaderRegistry.resolve(delegatedReq, realm).resolveImportedModule(ref, req).let { + when (it) { + // the loader returned `null`, opting out of resolution; continue with defaults + null -> super.resolveImportedModule(ref, req) + + // we're good to return from here + else -> it + } + } + + // the delegated registry has indicated there is no matching loader for this + false -> super.resolveImportedModule(ref, req) + } + } + + override fun resolveImportedModule(referencingModule: ScriptOrModule, moduleRequest: ModuleRequest): JSModuleRecord { + val requested = moduleRequest.specifier.toString() + val (prefix, unprefixed) = parsePrefixedMaybe(requested) + + return when (determineModuleStrategy(requested)) { + FALLBACK -> super.resolveImportedModule(referencingModule, moduleRequest) + DELEGATED -> resolveDelegatedImportedModule(referencingModule, moduleRequest, unprefixed) + SYNTHETIC -> injectedModuleCache.computeIfAbsent(unprefixed) { + synthesizeInjected(referencingModule, moduleRequest, prefix, unprefixed) + } + } + } + + override fun loadModule(moduleSource: Source, moduleData: JSModuleData): JSModuleRecord = + when (determineModuleStrategy(moduleSource)) { + // source modules (aside from meta-languages) are always handled by the base loader + FALLBACK -> super.loadModule(moduleSource, moduleData) + DELEGATED -> loadDelegatedSourceModule(moduleSource, moduleData) + else -> error("Mode is not supported for source loading") + } + + // Strategies for loading modules. + enum class ModuleStrategy { + // Delegate to other module loaders (for instance, for TypeScript or other meta-lang support). + DELEGATED, + + // Fallback to built-in behaviors. + FALLBACK, + + // Use a synthetic injected module. + SYNTHETIC, +; + } + + // Qualifies the type of synthesized module, as applicable. + enum class ModuleQualifier(override val symbol: String) : Symbolic { + BUN(BUN_MODULE_PREFIX), + DENO(DENO_MODULE_PREFIX), + ELIDE(ELIDE_MODULE_PREFIX), + NODE(NODE_MODULE_PREFIX), + ; + + companion object : Symbolic.SealedResolver { + override fun resolve(symbol: String): ModuleQualifier = when (symbol) { + DENO.symbol -> DENO + BUN.symbol -> BUN + NODE.symbol -> NODE + ELIDE.symbol -> ELIDE + else -> throw unresolved("Unknown module qualifier: $symbol") + } + } + } + + // Module record which holds a synthesized module object. + inner class ElideSyntheticModuleRecord( + @Suppress("UNUSED") val qualifier: ModuleQualifier, + data: JSModuleData, + module: Any + ) : JSModuleRecord( + data, + this, + module, + ) + + companion object : CommonJSModuleResolver { + private fun parsePrefixedMaybe(identifier: String): Pair { + val indexOfSplit = identifier.indexOf(':') + val prefix = if (indexOfSplit != -1) identifier.substring(0, indexOfSplit) else null + val unprefixed = prefix?.let { identifier.substring(indexOfSplit + 1, identifier.length) } ?: identifier + return prefix to unprefixed + } + + // Parse the module qualifier, if any, and return a `ModuleInfo` if the identifier represents a built-in module. + private fun toModuleInfo(identifier: String): ModuleInfo? { + val (_, unprefixed) = parsePrefixedMaybe(identifier) + return ModuleInfo.find(unprefixed) + } + + // Check an un-prefixed module name against the built-in modules. + private fun resolveUnprefixed(name: String): ModuleStrategy = when (name) { + in allElideModules, in allNodeModules -> SYNTHETIC + else -> DELEGATED + } + + // Determine the loading strategy to use for a given module request. + private fun determineModuleStrategy(source: Source): ModuleStrategy { + if (ALWAYS_FALLBACK) { + return FALLBACK + } + return when (source.language) { + // typescript/tsx/etc. is delegated, always + ELIDE_TS_LANGUAGE_ID -> DELEGATED + + // otherwise, fall back to regular module loader behavior + else -> FALLBACK + } + } + + // Determine the loading strategy to use for a given module request. + private fun determineModuleStrategy(requested: String): ModuleStrategy { + if (ALWAYS_FALLBACK) { + return FALLBACK + } + if (requested.contains(File.separatorChar) || requested.contains('.')) { + return FALLBACK // we are not a filesystem loader + } + + val (prefix, unprefixed) = parsePrefixedMaybe(requested) + return when (prefix) { + // un-prefixed modules may still be built-in (for example, `fs`). + null -> resolveUnprefixed(unprefixed) + + // special prefixes are always synthetic + in specialModulePrefixes -> SYNTHETIC + + // otherwise we try delegation + else -> FALLBACK + } + } + + @Suppress("UNUSED_PARAMETER") + private fun loadStaticDelegatedModule(realm: JSRealm, mod: ModuleInfo): CommonJSModuleProvider<*>? { + TODO("Delegated module loading in static contexts is not implemented yet") + } + + // Load a synthetic module in a static context; typically only from CommonJS. + private fun loadStaticSynthesizedModule(mod: ModuleInfo): CommonJSModuleProvider<*>? = when (mod) { + in ModuleRegistry -> object : CommonJSModuleProvider { + override fun provide(): Any = ModuleRegistry.load(mod) + } + else -> null + } + + // Resolver for CommonJS modules. + override fun resolve(realm: JSRealm, moduleIdentifier: String): CommonJSModuleProvider<*>? { + if (ALWAYS_FALLBACK) { + return null + } // bail out in always-fallback mode + val mod = toModuleInfo(moduleIdentifier) ?: return null // bail out if it's not a module we recognize + + return when (determineModuleStrategy(moduleIdentifier)) { + FALLBACK -> null // bail out if the strategy fn says so + DELEGATED -> loadStaticDelegatedModule(realm, mod) + SYNTHETIC -> loadStaticSynthesizedModule(mod) + } + } + + /** + * Create a new ESM loader; this method should be used with care (mostly for testing). + * + * @param realm The realm to install the loader to. + * @return The ES module loader singleton. + */ + @JvmStatic fun create(realm: JSRealm): ElideUniversalJsModuleLoader = ElideUniversalJsModuleLoader(realm) + } +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JSModuleProvider.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JSModuleProvider.kt new file mode 100644 index 0000000000..b3937da8e9 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JSModuleProvider.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import elide.runtime.gvm.loader.ModuleInfo + +/** + * ## JavaScript Module Provider + * + * This interface is implemented for classes which can resolve built-in JavaScript modules; usually, a module provider + * is paired with (or is also a) [SyntheticJSModule]. The module provider is responsible for bootstrapping or loading + * the module, as needed, and then providing an instance which satisfies the import request. + */ +public fun interface JSModuleProvider { + /** + * ## Resolve Module + * + * Given a suite of module info for a built-in module, resolve the module implementation and provide it as the return + * value. + * + * @param info The module info for the built-in module. + * @return The resolved module instance. + */ + public fun resolve(info: ModuleInfo): Any +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptExtensions.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptExtensions.kt new file mode 100644 index 0000000000..3cc34a2138 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptExtensions.kt @@ -0,0 +1,16 @@ +package elide.runtime.lang.javascript + +import com.oracle.js.parser.ir.Module.ModuleRequest +import elide.runtime.gvm.loader.ModuleInfo + +/** + * Create a [ModuleInfo] from a [ModuleRequest]. + * + * @param req The [ModuleRequest] to create a [ModuleInfo] from. + * @return The [ModuleInfo] corresponding to the [ModuleRequest]. + */ +internal fun ModuleInfo.Companion.from(req: ModuleRequest): ModuleInfo = requireNotNull( + allModuleInfos[req.specifier.toString().substringAfter(':')] +) { + "Module compile-time name '${req.specifier}' not found" +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptLang.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptLang.kt new file mode 100644 index 0000000000..030b11ebb2 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/JavaScriptLang.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import kotlinx.atomicfu.atomic + +/** + * ## JavaScript Language Utilities + * + * Static utilities for internal engine use. Manages initialization of key components within Elide's integration with + * GraalJs; this includes installing the [ElideJsModuleRouter]. + */ +public object JavaScriptLang { + // Whether JavaScript has initialized yet. + private val initialized = atomic(false) + + /** Initialize the JavaScript language layer. */ + public fun initialize() { + if (initialized.compareAndSet(expect = false, update = true)) { + ElideJsModuleRouter.install() + } + } +} diff --git a/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/SyntheticJSModule.kt b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/SyntheticJSModule.kt new file mode 100644 index 0000000000..2cbf3c2d62 --- /dev/null +++ b/packages/graalvm-js/src/main/kotlin/elide/runtime/lang/javascript/SyntheticJSModule.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Elide Technologies, Inc. + * + * Licensed under the MIT license (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://opensource.org/license/mit/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under the License. + */ + +package elide.runtime.lang.javascript + +import elide.runtime.gvm.loader.ModuleInfo +import elide.runtime.lang.javascript.SyntheticJSModule.ExportKind.* + +/** + * ## Synthetic JavaScript Module + * + * Describes a "synthetic" JavaScript module, which is a module that surfaces as a built-in for guest JavaScript code, + * and which is typically implemented in Kotlin or generally on JVM. Synthetic modules are resolved at their built-in + * name and routed to the appropriate implementation. + * + * This interface defines the API contract which is used to understand the module and to dynamically load it into a + * suite of exports. It is not a requirement that the synthetic facade for a module be the same as the module's actual + * implementation. + * + * @param T Type/shape of the module. + * @see JSModuleProvider ESM interface for synthetic modules. + * @see CommonJSModuleProvider CJS interface for synthetic modules. + */ +public interface SyntheticJSModule : JSModuleProvider, CommonJSModuleProvider { + /** + * Provide the module instance. + * + * @return Module instance. + */ + override fun provide(): T & Any + + /** + * Build a provider for this module. + * + * @return Provider for this module. + */ + public fun provider(): JSModuleProvider = JSModuleProvider { provide() } + + /** + * Exported symbols provided by this module. + * + * @return Array of exported symbols. + */ + public fun exports(): Array = emptyArray() + + // Use this provider to resolve the module. + override fun resolve(info: ModuleInfo): T & Any = provide() + + /** + * Describes different types of exports. + * + * - [CLASS] indicates a constructor export at a given name. + * - [DEFAULT] is a meta-type which wraps another type as the `default` module export. + * - [METHOD] indicates a module-level function (or method) at a given name. + * - [PROPERTY] indicates a module-level property at a given name. + */ + public enum class ExportKind { + CLASS, + DEFAULT, + METHOD, + PROPERTY, +; + } + + /** + * ## Exported Module Symbol + * + * Describes a symbol which is exported from a synthetic module; such symbols are used when initializing the module + * object in ESM mode, and used for generating bindings in string form for CJS mode. + * + * @param name Symbol to export this as; must be a valid identifier in JavaScript. + * @param kind Kind of export this is. + * @param from Optional source symbol for this symbol. + */ + @JvmRecord public data class ExportedSymbol( + val name: String, + val kind: ExportKind, + val from: String? = null, + ) { + public companion object { + @JvmStatic public fun of(name: String, kind: ExportKind = PROPERTY, from: String? = null): ExportedSymbol { + return ExportedSymbol(name, kind, from) + } + + @JvmStatic public fun default(from: String? = null): ExportedSymbol = of("default", DEFAULT, from) + + @JvmStatic public fun method(name: String, from: String? = null): ExportedSymbol = of(name, METHOD, from) + + @JvmStatic public fun cls(name: String, from: String? = null): ExportedSymbol = of(name, CLASS, from) + } + } +} diff --git a/packages/graalvm-py/api/graalvm-py.api b/packages/graalvm-py/api/graalvm-py.api index 184b08b2c2..d37cf640e7 100644 --- a/packages/graalvm-py/api/graalvm-py.api +++ b/packages/graalvm-py/api/graalvm-py.api @@ -39,10 +39,3 @@ public final class elide/runtime/plugins/python/features/JNIFeature : org/graalv public fun getDescription ()Ljava/lang/String; } -public class elide/runtime/python/ElidePythonLanguage : elide/runtime/plugins/api/NativePlugin { - public fun context (Lorg/graalvm/polyglot/Engine;Lorg/graalvm/polyglot/Context$Builder;[Ljava/lang/String;)V - public static fun get ()Lelide/runtime/python/ElidePythonLanguage; - public static fun init (Lorg/graalvm/word/Pointer;Lorg/graalvm/word/Pointer;J)V - public static fun main ([Ljava/lang/String;)V -} - diff --git a/packages/graalvm-py/src/main/java/elide/runtime/python/ElidePythonLanguage.java b/packages/graalvm-py/src/main/java/elide/runtime/python/ElidePythonLanguage.java deleted file mode 100644 index 7d76b1ce69..0000000000 --- a/packages/graalvm-py/src/main/java/elide/runtime/python/ElidePythonLanguage.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 Elide Technologies, Inc. - * - * Licensed under the MIT license (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://opensource.org/license/mit/ - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under the License. - */ -package elide.runtime.python; - -import elide.runtime.plugins.api.NativePlugin; -import org.graalvm.nativeimage.c.function.CEntryPoint; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Engine; -import org.graalvm.word.Pointer; - -/** TBD. */ -@SuppressWarnings("unused") -public class ElidePythonLanguage extends NativePlugin { - private static final String ELIDE_PYTHON = "python"; - private static final ElidePythonLanguage SINGLETON = new ElidePythonLanguage(); - private static final Context BASE = initialize(SINGLETON).build(); - - ElidePythonLanguage() { - super(ELIDE_PYTHON); - } - - public static ElidePythonLanguage get() { - return SINGLETON; - } - - @Override - public void context(Engine engine, Context.Builder builder, String[] args) { - builder.option("python.EmulateJython", "false"); - builder.option("python.NativeModules", "true"); - builder.option("python.LazyStrings", "true"); - builder.option("python.WithTRegex", "true"); - builder.option("python.WithCachedSources", "true"); - builder.option("python.HPyBackend", "nfi"); - builder.option("python.PosixModuleBackend", "java"); - } - - public static void main(String[] args) { - get().apply(args); - } - - @CEntryPoint(name = "Java_elide_runtime_python_ElidePythonLanguage_init") - public static void init( - Pointer jniEnv, Pointer clazz, @CEntryPoint.IsolateThreadContext long isolateId) { - System.out.println("Native Python plugin initialized (isolate: " + isolateId + ")"); - } -} diff --git a/packages/graalvm-py/src/main/kotlin/elide/runtime/plugins/python/Python.kt b/packages/graalvm-py/src/main/kotlin/elide/runtime/plugins/python/Python.kt index 593136ea52..e735d0340c 100644 --- a/packages/graalvm-py/src/main/kotlin/elide/runtime/plugins/python/Python.kt +++ b/packages/graalvm-py/src/main/kotlin/elide/runtime/plugins/python/Python.kt @@ -131,7 +131,6 @@ private val BUILTIN_PYTHON_PATHS = listOf( path.toString().startsWith(" FileSystem get() = { org.graalvm.python.embedding.utils.VirtualFileSystem .newBuilder() diff --git a/packages/graalvm-py/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI b/packages/graalvm-py/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI deleted file mode 100644 index b0728ecc67..0000000000 --- a/packages/graalvm-py/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI +++ /dev/null @@ -1 +0,0 @@ -elide.runtime.python.ElidePythonLanguage diff --git a/packages/graalvm-rb/api/graalvm-rb.api b/packages/graalvm-rb/api/graalvm-rb.api index 58e25ccd98..5cd474b6d0 100644 --- a/packages/graalvm-rb/api/graalvm-rb.api +++ b/packages/graalvm-rb/api/graalvm-rb.api @@ -60,6 +60,3 @@ public final class elide/runtime/ruby/ElideRuby { public static final fun main ([Ljava/lang/String;)V } -public class elide/runtime/ruby/ElideRubyLanguage : elide/runtime/plugins/api/NativePlugin { -} - diff --git a/packages/graalvm-rb/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI b/packages/graalvm-rb/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI deleted file mode 100644 index 041f9875cb..0000000000 --- a/packages/graalvm-rb/src/main/resources/META-INF/services/elide.runtime.plugins.api.NativePluginAPI +++ /dev/null @@ -1 +0,0 @@ -elide.runtime.ruby.ElideRubyLanguage diff --git a/packages/graalvm-ts/api/graalvm-ts.api b/packages/graalvm-ts/api/graalvm-ts.api index ec5947c347..6612820c1d 100644 --- a/packages/graalvm-ts/api/graalvm-ts.api +++ b/packages/graalvm-ts/api/graalvm-ts.api @@ -21,6 +21,13 @@ public class elide/runtime/lang/typescript/TypeScriptLanguage : com/oracle/truff protected fun parse (Lcom/oracle/truffle/api/TruffleLanguage$ParsingRequest;)Lcom/oracle/truffle/api/CallTarget; } +public class elide/runtime/lang/typescript/TypeScriptLanguage$TypeScriptLoaderFactory : elide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegateFactory { + public fun (Lelide/runtime/lang/typescript/TypeScriptLanguage;)V + public fun invoke (Lcom/oracle/truffle/js/runtime/JSRealm;)Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader; + public fun test (Lelide/runtime/lang/javascript/DelegatedModuleLoaderRegistry$DelegatedModuleRequest;)Z + public synthetic fun test (Ljava/lang/Object;)Z +} + public final class elide/runtime/lang/typescript/TypeScriptLanguageProvider : com/oracle/truffle/api/provider/TruffleLanguageProvider { public fun ()V } diff --git a/packages/graalvm-ts/build.gradle.kts b/packages/graalvm-ts/build.gradle.kts index a650bc3071..020f436e99 100644 --- a/packages/graalvm-ts/build.gradle.kts +++ b/packages/graalvm-ts/build.gradle.kts @@ -49,9 +49,13 @@ elide { dependencies { kapt(libs.graalvm.truffle.processor) api(projects.packages.engine) + api(projects.packages.graalvmJs) + api(libs.graalvm.truffle.api) implementation(libs.commons.io) implementation(libs.kotlinx.coroutines.core) implementation(libs.graalvm.js.language) + implementation(libs.graalvm.shadowed.icu4j) + implementation(libs.graalvm.regex) // Testing testImplementation(projects.packages.test) @@ -71,3 +75,16 @@ tasks { systemProperty("elide.test", "true") } } + +val (jsGroup, jsName) = libs.graalvm.js.language.get() + .let { + it.group to it.name + } +configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("$jsGroup:$jsName")).apply { + using(project(":packages:graalvm-js")) + because("Uses Elide's patched version of GraalJs") + } + } +} diff --git a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java index 647353876f..a5bdd12063 100644 --- a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java +++ b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -16,16 +16,17 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.TruffleLanguage.ContextPolicy; import com.oracle.truffle.api.TruffleLanguage.Registration; -import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.LanguageInfo; -import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSEngine; import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.objects.JSModuleLoader; +import elide.runtime.lang.javascript.DelegatedModuleLoaderRegistry; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.graalvm.polyglot.SandboxPolicy; +import org.jetbrains.annotations.NotNull; /** * TypeScript language implementation for GraalVM, meant for use via Elide. @@ -70,6 +71,19 @@ public class TypeScriptLanguage extends TruffleLanguage { private final AtomicBoolean compilerInitialized = new AtomicBoolean(false); private Env env; + public class TypeScriptLoaderFactory implements DelegatedModuleLoaderRegistry.DelegateFactory { + @Override + public @NotNull JSModuleLoader invoke(@NotNull JSRealm realm) { + return new TypeScriptModuleLoader(realm, tsCompiler); + } + + @Override + public boolean test( + DelegatedModuleLoaderRegistry.DelegatedModuleRequest delegatedModuleRequest) { + return false; + } + } + @Override protected JSRealm createContext(Env currentEnv) { CompilerAsserts.neverPartOfCompilation(); @@ -81,11 +95,10 @@ protected JSRealm createContext(Env currentEnv) { compilerInitialized.compareAndSet(false, true); tsCompiler = TypeScriptCompiler.obtain(jsEnv); env = jsEnv; + DelegatedModuleLoaderRegistry.register(new TypeScriptLoaderFactory()); } var ctx = JSEngine.createJSContext(js, jsEnv); - var realm = ctx.createRealm(jsEnv); - JSRealmPatcher.setTSModuleLoader(realm, new TypeScriptModuleLoader(realm, tsCompiler)); - return realm; + return ctx.createRealm(jsEnv); } @Override @@ -103,28 +116,6 @@ protected CallTarget parse(ParsingRequest parsingRequest) { tsSource.getCharacters(), tsSource.getName(), true, tsSource.getPath()); List argumentNames = parsingRequest.getArgumentNames(); var parsed = (RootCallTarget) env.parseInternal(jsSource, argumentNames.toArray(new String[0])); - var wrapper = new TSRootNode(this, parsed.getRootNode()); - return wrapper.getCallTarget(); - } - - private class TSRootNode extends RootNode { - private final RootNode delegate; - - protected TSRootNode(TruffleLanguage language, RootNode delegate) { - super(language); - this.delegate = delegate; - } - - @TruffleBoundary - private void setModuleLoader() { - JSRealm realm = JSRealm.get(delegate); - JSRealmPatcher.setTSModuleLoader(realm, new TypeScriptModuleLoader(realm, tsCompiler)); - } - - @Override - public Object execute(VirtualFrame frame) { - setModuleLoader(); - return delegate.execute(frame); - } + return parsed.getRootNode().getCallTarget(); } } diff --git a/packages/graalvm/api/graalvm.api b/packages/graalvm/api/graalvm.api index 0b33a8d157..804313a43e 100644 --- a/packages/graalvm/api/graalvm.api +++ b/packages/graalvm/api/graalvm.api @@ -1306,6 +1306,104 @@ public synthetic class elide/runtime/gvm/internals/vfs/$HostVFSImpl$HostVFSConfi public fun load ()Lio/micronaut/inject/BeanDefinition; } +public abstract class elide/runtime/gvm/internals/vfs/AbstractBaseVFS : elide/runtime/vfs/GuestVFS { + public static final field DEFAULT_CWD Ljava/lang/String; + public static final field ROOT_SYSTEM_DEFAULT Ljava/lang/String; + protected fun (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;)V + public abstract fun checkPolicy (Lelide/runtime/gvm/internals/vfs/AccessRequest;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public final fun checkPolicy (Lelide/runtime/gvm/internals/vfs/AccessType;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public static synthetic fun checkPolicy$default (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS;Lelide/runtime/gvm/internals/vfs/AccessType;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + protected final fun enforce (Lelide/runtime/gvm/internals/vfs/AccessType;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + protected final fun enforce (Ljava/util/EnumSet;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public static synthetic fun enforce$default (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS;Lelide/runtime/gvm/internals/vfs/AccessType;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public static synthetic fun enforce$default (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS;Ljava/util/EnumSet;Lelide/runtime/gvm/internals/vfs/AccessDomain;Ljava/nio/file/Path;Lelide/runtime/gvm/internals/vfs/AccessScope;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public abstract fun getLogging ()Lelide/runtime/Logger; + public abstract fun getPath ([Ljava/lang/String;)Ljava/nio/file/Path; + public abstract fun readStream (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream; + public abstract fun writeStream (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/OutputStream; +} + +public abstract interface class elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder { + public abstract fun build ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public abstract fun getBundleMapping ()Ljava/util/Map; + public abstract fun getCaseSensitive ()Z + public abstract fun getDeferred ()Z + public abstract fun getEnableSymlinks ()Z + public abstract fun getPolicy ()Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy; + public abstract fun getReadOnly ()Z + public abstract fun getRegistry ()Ljava/util/Map; + public abstract fun getRoot ()Ljava/lang/String; + public abstract fun getWorkingDirectory ()Ljava/lang/String; + public fun setBundleMapping (Ljava/util/Map;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setBundleMapping (Ljava/util/Map;)V + public fun setCaseSensitive (Z)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setCaseSensitive (Z)V + public fun setDeferred (Z)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setDeferred (Z)V + public fun setEnableSymlinks (Z)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setEnableSymlinks (Z)V + public fun setPolicy (Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setPolicy (Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;)V + public fun setReadOnly (Z)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setReadOnly (Z)V + public fun setRegistry (Ljava/util/Map;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setRegistry (Ljava/util/Map;)V + public fun setRoot (Ljava/lang/String;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setRoot (Ljava/lang/String;)V + public fun setWorkingDirectory (Ljava/lang/String;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun setWorkingDirectory (Ljava/lang/String;)V +} + +public abstract interface class elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilderFactory { + public abstract fun newBuilder ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public abstract fun newBuilder (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; +} + +public abstract interface class elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSFactory { + public abstract fun create ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public abstract fun create (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public abstract fun create (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public abstract fun create (Lkotlin/jvm/functions/Function1;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; +} + +public abstract class elide/runtime/gvm/internals/vfs/AbstractDelegateVFS : elide/runtime/gvm/internals/vfs/AbstractBaseVFS, elide/runtime/vfs/GuestVFS { + protected fun (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;Ljava/nio/file/FileSystem;Ljava/util/concurrent/atomic/AtomicReference;)V + public synthetic fun (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;Ljava/nio/file/FileSystem;Ljava/util/concurrent/atomic/AtomicReference;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun checkAccess (Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/LinkOption;)V + public fun checkPolicy (Lelide/runtime/gvm/internals/vfs/AccessRequest;)Lelide/runtime/gvm/internals/vfs/AccessResponse; + public fun close ()V + public fun copy (Ljava/nio/file/Path;Ljava/nio/file/Path;[Ljava/nio/file/CopyOption;)V + public fun createDirectory (Ljava/nio/file/Path;[Ljava/nio/file/attribute/FileAttribute;)V + public fun createLink (Ljava/nio/file/Path;Ljava/nio/file/Path;)V + public fun createSymbolicLink (Ljava/nio/file/Path;Ljava/nio/file/Path;[Ljava/nio/file/attribute/FileAttribute;)V + protected final fun debugLog (Lkotlin/jvm/functions/Function0;)V + public fun delete (Ljava/nio/file/Path;)V + protected final fun getBacking ()Ljava/nio/file/FileSystem; + public fun getEncoding (Ljava/nio/file/Path;)Ljava/nio/charset/Charset; + public fun getMimeType (Ljava/nio/file/Path;)Ljava/lang/String; + public fun getPath ([Ljava/lang/String;)Ljava/nio/file/Path; + public fun getPathSeparator ()Ljava/lang/String; + public fun getSeparator ()Ljava/lang/String; + public fun getTempDirectory ()Ljava/nio/file/Path; + public fun isSameFile (Ljava/nio/file/Path;Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z + public fun move (Ljava/nio/file/Path;Ljava/nio/file/Path;[Ljava/nio/file/CopyOption;)V + public fun newByteChannel (Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/channels/SeekableByteChannel; + public fun newDirectoryStream (Ljava/nio/file/Path;Ljava/nio/file/DirectoryStream$Filter;)Ljava/nio/file/DirectoryStream; + protected fun notAllowed (Ljava/util/Set;Ljava/nio/file/Path;Ljava/lang/String;)Lelide/runtime/gvm/internals/vfs/GuestIOException; + public static synthetic fun notAllowed$default (Lelide/runtime/gvm/internals/vfs/AbstractDelegateVFS;Ljava/util/Set;Ljava/nio/file/Path;Ljava/lang/String;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/GuestIOException; + public fun parsePath (Ljava/lang/String;)Ljava/nio/file/Path; + public fun parsePath (Ljava/net/URI;)Ljava/nio/file/Path; + public fun readAttributes (Ljava/nio/file/Path;Ljava/lang/String;[Ljava/nio/file/LinkOption;)Ljava/util/Map; + public fun readStream (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream; + public fun readSymbolicLink (Ljava/nio/file/Path;)Ljava/nio/file/Path; + public fun setAttribute (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/Object;[Ljava/nio/file/LinkOption;)V + public fun setCurrentWorkingDirectory (Ljava/nio/file/Path;)V + public fun toAbsolutePath (Ljava/nio/file/Path;)Ljava/nio/file/Path; + public fun toRealPath (Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Ljava/nio/file/Path; + public static final fun withConfig$graalvm (Lelide/runtime/gvm/cfg/GuestIOConfiguration;)Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig; + public fun writeStream (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/OutputStream; +} + public final class elide/runtime/gvm/internals/vfs/AccessDomain : java/lang/Enum { public static final field GUEST Lelide/runtime/gvm/internals/vfs/AccessDomain; public static final field HOST Lelide/runtime/gvm/internals/vfs/AccessDomain; @@ -1397,6 +1495,167 @@ public final class elide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig { public static final fun withPolicy (Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;ZZLjava/lang/String;Ljava/lang/String;)Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig; } +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl : elide/runtime/gvm/internals/vfs/AbstractDelegateVFS { + public static final field EmbeddedVFSFactory Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$EmbeddedVFSFactory; + public synthetic fun (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;Ljava/nio/file/FileSystem;Ltools/elide/vfs/Filesystem;ZLjava/util/Map;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun allowsHostFileAccess ()Z + public fun allowsHostSocketAccess ()Z + public fun existsAny (Ljava/nio/file/Path;)Z + public fun getCompound ()Z + public fun getDeletable ()Z + public fun getHost ()Z + public fun getLogging ()Lelide/runtime/Logger; + public fun getSupportsSymlinks ()Z + public fun getVirtual ()Z + public fun getWritable ()Z + public static final fun loadBundles$graalvm (Ljava/util/List;Ljava/util/List;Lcom/google/common/jimfs/Configuration$Builder;ZLjava/util/Map;Ljava/util/Map;)Lkotlin/Triple; + public static final fun loadWithFileTarget$graalvm (Ljava/net/URI;)Lkotlin/Triple; + public fun newByteChannel (Ljava/nio/file/Path;Ljava/util/Set;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/channels/SeekableByteChannel; + public fun newDirectoryStream (Ljava/nio/file/Path;Ljava/nio/file/DirectoryStream$Filter;)Ljava/nio/file/DirectoryStream; + public fun readAttributes (Ljava/nio/file/Path;Ljava/lang/String;[Ljava/nio/file/LinkOption;)Ljava/util/Map; + public fun readStream (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream; + public fun readSymbolicLink (Ljava/nio/file/Path;)Ljava/nio/file/Path; + public static final fun resolveBundles$graalvm (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder;Lcom/google/common/jimfs/Configuration$Builder;Ljava/util/concurrent/ConcurrentMap;Ljava/util/Map;)Lkotlin/Triple; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder : elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder { + public static final field Factory Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder$Factory; + public fun ()V + public fun (ZZZZLjava/lang/String;Ljava/lang/String;Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;Ljava/util/Map;Ljava/util/Map;Lkotlin/Pair;Ljava/util/List;Ljava/util/List;Ljava/net/URI;Ljava/net/URI;)V + public synthetic fun (ZZZZLjava/lang/String;Ljava/lang/String;Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;Ljava/util/Map;Ljava/util/Map;Lkotlin/Pair;Ljava/util/List;Ljava/util/List;Ljava/net/URI;Ljava/net/URI;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun build ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public fun build ()Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl; + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy; + public final fun component8 ()Ljava/util/Map; + public final fun component9 ()Ljava/util/Map; + public final fun copy (ZZZZLjava/lang/String;Ljava/lang/String;Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;Ljava/util/Map;Ljava/util/Map;Lkotlin/Pair;Ljava/util/List;Ljava/util/List;Ljava/net/URI;Ljava/net/URI;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder; + public static synthetic fun copy$default (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder;ZZZZLjava/lang/String;Ljava/lang/String;Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;Ljava/util/Map;Ljava/util/Map;Lkotlin/Pair;Ljava/util/List;Ljava/util/List;Ljava/net/URI;Ljava/net/URI;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder; + public fun equals (Ljava/lang/Object;)Z + public fun getBundleMapping ()Ljava/util/Map; + public fun getCaseSensitive ()Z + public fun getDeferred ()Z + public fun getEnableSymlinks ()Z + public fun getPolicy ()Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy; + public fun getReadOnly ()Z + public fun getRegistry ()Ljava/util/Map; + public fun getRoot ()Ljava/lang/String; + public fun getWorkingDirectory ()Ljava/lang/String; + public fun hashCode ()I + public final fun setBundle (Lkotlin/Pair;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public final fun setBundle (Lkotlin/Triple;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public final fun setBundleFiles (Ljava/util/List;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun setBundleMapping (Ljava/util/Map;)V + public final fun setBundlePaths (Ljava/util/List;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun setCaseSensitive (Z)V + public fun setDeferred (Z)V + public fun setEnableSymlinks (Z)V + public final fun setFileTarget (Ljava/io/File;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public final fun setFileTarget (Ljava/net/URI;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public final fun setFileTarget (Ljava/nio/file/Path;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun setPolicy (Lelide/runtime/gvm/internals/vfs/GuestVFSPolicy;)V + public fun setReadOnly (Z)V + public fun setRegistry (Ljava/util/Map;)V + public fun setRoot (Ljava/lang/String;)V + public fun setWorkingDirectory (Ljava/lang/String;)V + public final fun setZipTarget (Ljava/net/URI;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun toString ()Ljava/lang/String; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder$Factory : elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilderFactory { + public synthetic fun newBuilder ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun newBuilder ()Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder; + public synthetic fun newBuilder (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder; + public fun newBuilder (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$Builder; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat : java/lang/Enum { + public static final field ELIDE_INTERNAL Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static final field TARBALL Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static final field TARBALL_GZIP Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static final field TARBALL_XZ Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static final field ZIP Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public static fun values ()[Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo : java/lang/Record { + public static final field Companion Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo$Companion; + public fun (ILjava/lang/String;Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat;)V + public static final fun buildFor (Ljava/util/List;)Ljava/util/Map; + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; + public final fun copy (ILjava/lang/String;Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo; + public static synthetic fun copy$default (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo;ILjava/lang/String;Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun id ()I + public final fun location ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; + public final fun type ()Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleFormat; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$BundleInfo$Companion { + public final fun buildFor (Ljava/util/List;)Ljava/util/Map; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$EmbeddedVFSFactory : elide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSFactory { + public synthetic fun create ()Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public fun create ()Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl; + public synthetic fun create (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public fun create (Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS$VFSBuilder;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl; + public synthetic fun create (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public fun create (Lelide/runtime/gvm/internals/vfs/EffectiveGuestVFSConfig;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl; + public synthetic fun create (Lkotlin/jvm/functions/Function1;)Lelide/runtime/gvm/internals/vfs/AbstractBaseVFS; + public fun create (Lkotlin/jvm/functions/Function1;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsDirectory : java/lang/Record, elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsObjectInfo { + public fun (Ljava/lang/String;ILtools/elide/vfs/Directory;)V + public fun bundle ()I + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()I + public final fun component3 ()Ltools/elide/vfs/Directory; + public final fun copy (Ljava/lang/String;ILtools/elide/vfs/Directory;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsDirectory; + public static synthetic fun copy$default (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsDirectory;Ljava/lang/String;ILtools/elide/vfs/Directory;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsDirectory; + public fun equals (Ljava/lang/Object;)Z + public synthetic fun getBundle ()I + public synthetic fun getPath ()Ljava/lang/String; + public fun hashCode ()I + public final fun info ()Ltools/elide/vfs/Directory; + public fun path ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsFileInfo : java/lang/Record, elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsObjectInfo { + public fun (Ljava/lang/String;ILtools/elide/vfs/File;)V + public fun bundle ()I + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()I + public final fun component3 ()Ltools/elide/vfs/File; + public final fun copy (Ljava/lang/String;ILtools/elide/vfs/File;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsFileInfo; + public static synthetic fun copy$default (Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsFileInfo;Ljava/lang/String;ILtools/elide/vfs/File;ILjava/lang/Object;)Lelide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsFileInfo; + public fun equals (Ljava/lang/Object;)Z + public synthetic fun getBundle ()I + public synthetic fun getPath ()Ljava/lang/String; + public fun hashCode ()I + public final fun info ()Ltools/elide/vfs/File; + public fun path ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public abstract interface class elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl$VfsObjectInfo { + public abstract fun getBundle ()I + public abstract fun getPath ()Ljava/lang/String; +} + public abstract class elide/runtime/gvm/internals/vfs/GuestIOException : java/io/IOException { public fun ()V public fun (Ljava/lang/String;Ljava/lang/Throwable;)V @@ -1486,6 +1745,11 @@ public final class elide/runtime/gvm/js/JavaScript$Inputs { public static synthetic fun requestState$default (Lelide/ssr/type/RequestState;Ljava/lang/Object;ILjava/lang/Object;)Lelide/runtime/gvm/RequestExecutionInputs; } +public final class elide/runtime/gvm/js/JavaScriptKt { + public static final fun nullvalue ()Lcom/oracle/truffle/js/runtime/objects/JSDynamicObject; + public static final fun undefined ()Lcom/oracle/truffle/js/runtime/objects/JSDynamicObject; +} + public final class elide/runtime/gvm/js/JsError { public static final field INSTANCE Lelide/runtime/gvm/js/JsError; public final fun error (Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/Integer;[Lkotlin/Pair;)Ljava/lang/Void; @@ -2415,8 +2679,7 @@ public abstract interface class elide/runtime/intrinsics/js/node/AssertAPI : eli public static synthetic fun notEqual$default (Lelide/runtime/intrinsics/js/node/AssertAPI;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun notOk (Ljava/lang/Object;Ljava/lang/Object;)V public static synthetic fun notOk$default (Lelide/runtime/intrinsics/js/node/AssertAPI;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)V - public abstract fun ok (Ljava/lang/Object;Ljava/lang/Object;)V - public static synthetic fun ok$default (Lelide/runtime/intrinsics/js/node/AssertAPI;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)V + public abstract fun ok ([Ljava/lang/Object;)V public abstract fun rejects (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V public static synthetic fun rejects$default (Lelide/runtime/intrinsics/js/node/AssertAPI;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)V public abstract fun strict (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V @@ -5785,7 +6048,7 @@ public synthetic class elide/runtime/node/childProcess/$NodeChildProcessModule$I public fun load ()Lio/micronaut/inject/BeanDefinition; } -public synthetic class elide/runtime/node/childProcess/$NodeChildProcessModule$Provide$graalvm0$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference { +public synthetic class elide/runtime/node/childProcess/$NodeChildProcessModule$Provide0$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference { public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata; public fun ()V protected fun (Ljava/lang/Class;Lio/micronaut/context/AbstractInitializableBeanDefinition$MethodOrFieldReference;)V @@ -6680,6 +6943,17 @@ public synthetic class elide/runtime/node/path/$NodePathsModule$Definition : io/ public fun load ()Lio/micronaut/inject/BeanDefinition; } +public synthetic class elide/runtime/node/path/$NodePathsModule$Provide0$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference { + public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata; + public fun ()V + protected fun (Ljava/lang/Class;Lio/micronaut/context/AbstractInitializableBeanDefinition$MethodOrFieldReference;)V + public fun inject (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;Ljava/lang/Object;)Ljava/lang/Object; + public fun instantiate (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;)Ljava/lang/Object; + public fun isEnabled (Lio/micronaut/context/BeanContext;)Z + public fun isEnabled (Lio/micronaut/context/BeanContext;Lio/micronaut/context/BeanResolutionContext;)Z + public fun load ()Lio/micronaut/inject/BeanDefinition; +} + public final class elide/runtime/node/path/PathBuf : elide/runtime/intrinsics/js/node/path/Path, org/graalvm/polyglot/proxy/ProxyObject { public static final field Companion Lelide/runtime/node/path/PathBuf$Companion; public synthetic fun (Lelide/runtime/node/path/PathStyle;Lkotlinx/io/files/Path;Lkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -7047,17 +7321,6 @@ public synthetic class elide/runtime/node/zlib/$NodeZlibModule$Definition : io/m public fun load ()Lio/micronaut/inject/BeanDefinition; } -public synthetic class elide/runtime/node/zlib/$NodeZlibModule$Provide$graalvm0$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference { - public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata; - public fun ()V - protected fun (Ljava/lang/Class;Lio/micronaut/context/AbstractInitializableBeanDefinition$MethodOrFieldReference;)V - public fun inject (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;Ljava/lang/Object;)Ljava/lang/Object; - public fun instantiate (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;)Ljava/lang/Object; - public fun isEnabled (Lio/micronaut/context/BeanContext;)Z - public fun isEnabled (Lio/micronaut/context/BeanContext;Lio/micronaut/context/BeanResolutionContext;)Z - public fun load ()Lio/micronaut/inject/BeanDefinition; -} - public final synthetic class elide/runtime/node/zlib/$NodeZlibModule$ReflectConfig : io/micronaut/core/graal/GraalReflectionConfigurer { public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata; public fun ()V diff --git a/packages/graalvm/build.gradle.kts b/packages/graalvm/build.gradle.kts index 9fcdbdbbc3..05a65dbc60 100644 --- a/packages/graalvm/build.gradle.kts +++ b/packages/graalvm/build.gradle.kts @@ -448,6 +448,10 @@ dependencies { api(projects.packages.graalvmTs) api(projects.packages.graalvmWasm) + // GraalVM / Truffle + api(libs.graalvm.truffle.api) + api(libs.graalvm.truffle.runtime) + // Kotlin / KotlinX implementation(kotlin("stdlib")) implementation(kotlin("reflect")) @@ -669,3 +673,15 @@ if (enableBenchmarks) afterEvaluate { }.toString()) } } + +val (jsGroup, jsName) = libs.graalvm.js.language.get().let { + it.group to it.name +} +configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("${jsGroup}:${jsName}")).apply { + using(project(":packages:graalvm-js")) + because("Uses Elide's patched version of GraalJs") + } + } +} diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt b/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt index e2f53afdd4..d0b62ecee8 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt @@ -14,6 +14,7 @@ package elide.runtime.core.internals.graalvm +import com.oracle.truffle.js.lang.JavaScriptLanguage import org.graalvm.nativeimage.ImageInfo import org.graalvm.nativeimage.Platform import org.graalvm.polyglot.Context @@ -42,6 +43,7 @@ import elide.runtime.core.internals.MutableEngineLifecycle import elide.runtime.core.internals.graalvm.GraalVMEngine.Companion.create import elide.runtime.core.internals.graalvm.GraalVMRuntime.Companion.GVM_23 import elide.runtime.core.internals.graalvm.GraalVMRuntime.Companion.GVM_23_1 +import elide.runtime.lang.typescript.TypeScriptLanguage import elide.vm.annotations.Polyglot import org.graalvm.polyglot.HostAccess as PolyglotHostAccess @@ -124,7 +126,7 @@ import org.graalvm.polyglot.HostAccess as PolyglotHostAccess // Finalize a suite of bindings for a given language (or the main polyglot bindings). private fun finalizeBindings(bindings: Value) { - if (bindings.hasMembers()) { + if (EXPERIMENTAL_DROP_INTERNALS && bindings.hasMembers()) { knownInternalMembers.forEach { try { bindings.removeMember(it) @@ -219,7 +221,7 @@ import org.graalvm.polyglot.HostAccess as PolyglotHostAccess private const val ENABLE_AUX_CACHE = false /** Whether to drop internals from the polyglot context before finalization completes. */ - private const val EXPERIMENTAL_DROP_INTERNALS = true + private const val EXPERIMENTAL_DROP_INTERNALS = false /** Whether internal symbols should be withheld from guest code. */ private val shouldDropInternals = System.getProperty("elide.internals") != "true" @@ -252,7 +254,17 @@ import org.graalvm.polyglot.HostAccess as PolyglotHostAccess @Suppress("SpreadOperator", "LongMethod") public fun create(configuration: GraalVMConfiguration, lifecycle: MutableEngineLifecycle): GraalVMEngine { val nativesPath = System.getProperty("elide.natives")?.ifBlank { null } ?: defaultAuxPath - val languages = configuration.languages.map { it.languageId }.toTypedArray() + val languages = configuration.languages.flatMap { + when (it.languageId) { + JavaScriptLanguage.ID, + TypeScriptLanguage.ID -> listOf( + JavaScriptLanguage.ID, + TypeScriptLanguage.ID, + ) + + else -> listOf(it.languageId) + } + }.distinct().toTypedArray() val builder = Engine.newBuilder(*languages).apply { useSystemProperties(false) diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/console/ConsoleIntrinsic.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/console/ConsoleIntrinsic.kt index 0965c7cb4e..744dbde51a 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/console/ConsoleIntrinsic.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/console/ConsoleIntrinsic.kt @@ -160,7 +160,7 @@ internal class ConsoleIntrinsic : JavaScriptConsole, AbstractJsIntrinsic() { arg.isNativePointer -> "NativePointer(${arg.asNativePointer()})" arg.isIterator -> "Iterator(...)" arg.isMetaObject -> {} - arg.isBufferWritable -> {} + arg.hasBufferElements() -> {} // temporal types arg.isDate -> arg.asDate().toString() diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractBaseVFS.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractBaseVFS.kt index d38cf632b8..1119bbed4c 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractBaseVFS.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractBaseVFS.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -39,7 +39,7 @@ import elide.runtime.gvm.internals.vfs.EmbeddedGuestVFSImpl.VfsObjectInfo * @param VFS Concrete virtual file system type under implementation. * @param config Effective guest VFS configuration to apply. */ -internal abstract class AbstractBaseVFS protected constructor ( +public abstract class AbstractBaseVFS protected constructor ( internal val config: EffectiveGuestVFSConfig, ) : GuestVFS where VFS: AbstractBaseVFS { internal companion object { @@ -59,7 +59,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @param VFS Concrete virtual file system type under implementation. */ - interface VFSBuilder where VFS: AbstractBaseVFS { + public interface VFSBuilder where VFS: AbstractBaseVFS { /** * ### Deferred mode * @@ -70,7 +70,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setDeferred for the builder-method equivalent. */ - var deferred: Boolean + public var deferred: Boolean /** * ### Path Registry @@ -82,7 +82,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setRegistry for the builder-method equivalent. */ - var registry: MutableMap + public var registry: MutableMap /** * ### Bundle Mapping @@ -94,7 +94,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setBundleMapping for the builder-method equivalent. */ - var bundleMapping: MutableMap + public var bundleMapping: MutableMap /** * ### Read-only status @@ -106,7 +106,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setReadOnly for the builder-method equivalent. */ - var readOnly: Boolean + public var readOnly: Boolean /** * ### Case-sensitivity @@ -118,7 +118,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setCaseSensitive for the builder-method equivalent. */ - var caseSensitive: Boolean + public var caseSensitive: Boolean /** * ### Symlink support @@ -131,7 +131,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setEnableSymlinks for the builder-method equivalent. */ - var enableSymlinks: Boolean + public var enableSymlinks: Boolean /** * ### Root path @@ -143,7 +143,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setRoot for the builder-method equivalent. */ - var root: String + public var root: String /** * ### Working directory @@ -155,7 +155,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setWorkingDirectory for the builder-method equivalent. */ - var workingDirectory: String + public var workingDirectory: String /** * ### Guest I/O policy @@ -168,7 +168,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see setPolicy for the builder-method equivalent. */ - var policy: GuestVFSPolicy + public var policy: GuestVFSPolicy /** * Set the [deferred] status of the file-system managed by this VFS implementation. @@ -177,7 +177,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param deferred Whether the file-system should be considered read-only. * @return This builder. */ - fun setDeferred(deferred: Boolean): VFSBuilder { + public fun setDeferred(deferred: Boolean): VFSBuilder { this.deferred = deferred return this } @@ -189,7 +189,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param registry Registry of known file paths, produced by indexing bundles. * @return This builder. */ - fun setRegistry(registry: MutableMap): VFSBuilder { + public fun setRegistry(registry: MutableMap): VFSBuilder { this.registry = registry return this } @@ -201,7 +201,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param bundles Map of indexed bundles for deferred reads. * @return This builder. */ - fun setBundleMapping(bundles: Map): VFSBuilder { + public fun setBundleMapping(bundles: Map): VFSBuilder { this.bundleMapping = bundles.toMutableMap() return this } @@ -213,7 +213,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param readOnly Whether the file-system should be considered read-only. * @return This builder. */ - fun setReadOnly(readOnly: Boolean): VFSBuilder { + public fun setReadOnly(readOnly: Boolean): VFSBuilder { this.readOnly = readOnly return this } @@ -225,7 +225,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param caseSensitive Whether the file-system should be considered case-sensitive. * @return This builder. */ - fun setCaseSensitive(caseSensitive: Boolean): VFSBuilder { + public fun setCaseSensitive(caseSensitive: Boolean): VFSBuilder { this.caseSensitive = caseSensitive return this } @@ -237,7 +237,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param enableSymlinks Whether the file-system should expect symbolic link support. * @return This builder. */ - fun setEnableSymlinks(enableSymlinks: Boolean): VFSBuilder { + public fun setEnableSymlinks(enableSymlinks: Boolean): VFSBuilder { this.enableSymlinks = enableSymlinks return this } @@ -249,7 +249,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param root Root file path to apply. * @return This builder. */ - fun setRoot(root: String): VFSBuilder { + public fun setRoot(root: String): VFSBuilder { this.root = root return this } @@ -261,7 +261,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param workingDirectory Current-working-directory file path to apply. * @return This builder. */ - fun setWorkingDirectory(workingDirectory: String): VFSBuilder { + public fun setWorkingDirectory(workingDirectory: String): VFSBuilder { this.workingDirectory = workingDirectory return this } @@ -273,7 +273,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param policy Policy to apply. * @return This builder. */ - fun setPolicy(policy: GuestVFSPolicy): VFSBuilder { + public fun setPolicy(policy: GuestVFSPolicy): VFSBuilder { this.policy = policy return this } @@ -283,7 +283,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @return Built VFS implementation. */ - fun build(): VFS + public fun build(): VFS } /** @@ -294,20 +294,20 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @see newBuilder to create an empty VFS implementation builder, or to clone an existing builder. */ - interface VFSBuilderFactory where VFS: AbstractBaseVFS, Builder: VFSBuilder { + public interface VFSBuilderFactory where VFS: AbstractBaseVFS, Builder: VFSBuilder { /** * Create a new VFS implementation builder, of type [VFS]. * * @return Empty VFS implementation builder. */ - fun newBuilder(): Builder + public fun newBuilder(): Builder /** * Clone the provided [builder] to create a new builder. * * @return Cloned VFS implementation builder. */ - fun newBuilder(builder: Builder): Builder + public fun newBuilder(builder: Builder): Builder } /** @@ -316,13 +316,13 @@ internal abstract class AbstractBaseVFS protected constructor ( * Specifies the expected API surface of a VFS implementation's companion object, which should be equipped to create * and resolve VFS implementations using the [VFSBuilder] and [VFSBuilderFactory]. */ - interface VFSFactory where VFS: AbstractBaseVFS, Builder: VFSBuilder { + public interface VFSFactory where VFS: AbstractBaseVFS, Builder: VFSBuilder { /** * Create a [VFS] implementation with no backing data, and configured with defaults. * * @return Empty and default-configured [VFS] implementation. */ - fun create(): VFS + public fun create(): VFS /** * Create a [VFS] implementation configured with the provided [configurator]. @@ -332,7 +332,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param configurator Function to execute, in the context of [Builder], which prepares the VFS implementation. * @return Build implementation of type [VFS]. */ - fun create(configurator: Builder.() -> Unit): VFS + public fun create(configurator: Builder.() -> Unit): VFS /** * Create a [VFS] implementation configured with the provided effective [config]. @@ -340,7 +340,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param config Configuration to provide to the VFS implementation. * @return VFS implementation built with the provided [config]. */ - fun create(config: EffectiveGuestVFSConfig): VFS + public fun create(config: EffectiveGuestVFSConfig): VFS /** * Create a [VFS] implementation from the provided [builder]. @@ -348,7 +348,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param builder Builder to create the VFS implementation from. * @return VFS implementation built with the provided [builder]. */ - fun create(builder: VFSBuilder): VFS + public fun create(builder: VFSBuilder): VFS } /** @@ -356,7 +356,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * @return [Logger] that should be used for debug messages emitted from this VFS implementation. */ - abstract val logging: Logger + public abstract val logging: Logger /** * Subclass API: Enforcement. @@ -428,7 +428,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param scope Whether this path is known to be a file, or directory, or it is not known. * @return Response from the policy check, indicating whether the request is allowed. */ - fun checkPolicy( + public fun checkPolicy( type: AccessType, domain: AccessDomain, path: Path, @@ -475,7 +475,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param request Materialized I/O policy check request. * @return Response from the policy check, indicating whether the request is allowed. */ - abstract fun checkPolicy(request: AccessRequest): AccessResponse + public abstract fun checkPolicy(request: AccessRequest): AccessResponse /** * Subclass API: Parse a path. @@ -487,7 +487,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @param segments Path segments to parse. * @return Parsed path. */ - internal abstract fun getPath(vararg segments: String): Path + public abstract fun getPath(vararg segments: String): Path /** * Subclass API: Read a file. @@ -498,7 +498,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * Host reads are still subject to any policy restrictions and isolation: for example, if an embedded VFS backed by a * file or jailed directory is used, the host reads are still scoped to these policies. To control host reads within - * the scope of the VFS system, a sub-class may override this method or [checkPolicy]. + * the scope of the VFS system, a subclass may override this method or [checkPolicy]. * * @param path Path of the file to read. * @param options Open options to use when reading the file. @@ -507,7 +507,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * @throws AccessDeniedException if the read operation cannot be completed because of a policy violation. */ @Throws(IOException::class) - internal abstract fun readStream(path: Path, vararg options: OpenOption): InputStream + public abstract fun readStream(path: Path, vararg options: OpenOption): InputStream /** * Subclass API: Write a file. @@ -518,7 +518,7 @@ internal abstract class AbstractBaseVFS protected constructor ( * * Host writes are still subject to any policy restrictions and isolation: for example, if an embedded VFS backed by a * file or jailed directory is used, the host writes are still scoped to these policies. To control host writes within - * the scope of the VFS system, a sub-class may override this method or [checkPolicy]. + * the scope of the VFS system, a subclass may override this method or [checkPolicy]. * * @param path Path of the file we intend to write to. * @param options Open options to use when writing to the file. @@ -526,5 +526,5 @@ internal abstract class AbstractBaseVFS protected constructor ( * @throws IOException if the write operation cannot be completed. * @throws AccessDeniedException if the write operation cannot be completed because of a policy violation. */ - internal abstract fun writeStream(path: Path, vararg options: OpenOption): OutputStream + public abstract fun writeStream(path: Path, vararg options: OpenOption): OutputStream } diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractDelegateVFS.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractDelegateVFS.kt index 59daa26ecb..ba29b38261 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractDelegateVFS.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/AbstractDelegateVFS.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -43,7 +43,7 @@ import elide.runtime.vfs.GuestVFS * @param config Effective guest VFS configuration to apply. * @param backing Backing file-system instance which implements the FS to use. */ -internal abstract class AbstractDelegateVFS protected constructor ( +public abstract class AbstractDelegateVFS protected constructor ( config: EffectiveGuestVFSConfig, protected val backing: FileSystem, private val activeWorkingDirectory: AtomicReference = AtomicReference(Path.of(config.workingDirectory)), diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl.kt index 693820ef30..bcabdaef89 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/vfs/EmbeddedGuestVFSImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -94,7 +94,7 @@ private val bundleCache: MutableMap> = Has */ @Requires(property = "elide.gvm.vfs.enabled", notEquals = "false") @Requires(property = "elide.gvm.vfs.mode", notEquals = "HOST") -internal class EmbeddedGuestVFSImpl private constructor ( +public class EmbeddedGuestVFSImpl private constructor ( config: EffectiveGuestVFSConfig, backing: FileSystem, private val tree: FilesystemInfo, @@ -115,7 +115,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( } /** Enumerates supported embedded VFS bundle formats. */ - enum class BundleFormat { + public enum class BundleFormat { /** Regular (non-compressed) tarball. */ TARBALL, @@ -138,39 +138,39 @@ internal class EmbeddedGuestVFSImpl private constructor ( } // Baseline VFS metadata. - sealed interface VfsObjectInfo { - val path: String - val bundle: Int + public sealed interface VfsObjectInfo { + public val path: String + public val bundle: Int } // VFS metadata for a file. - @JvmRecord data class VfsFileInfo( + @JvmRecord public data class VfsFileInfo( override val path: String, override val bundle: Int, val info: tools.elide.vfs.File, ) : VfsObjectInfo // VFS metadata for a directory. - @JvmRecord data class VfsDirectory( + @JvmRecord public data class VfsDirectory( override val path: String, override val bundle: Int, val info: tools.elide.vfs.Directory, ) : VfsObjectInfo // Describes a bundle under access for VFS. - @JvmRecord internal data class BundleInfo( + @JvmRecord public data class BundleInfo( /** Internal (local) ID for the bundle. */ - val id: Int, + public val id: Int, /** URI or symbolic location for the bundle. */ - val location: String, + public val location: String, /** Format for the bundle. */ - val type: BundleFormat, + public val type: BundleFormat, ) { - companion object { + public companion object { // Build a map of locally-identified bundles. - @JvmStatic fun buildFor(bundles: List>): Map = + @JvmStatic public fun buildFor(bundles: List>): Map = bundles.associate { (id, uri, type) -> id to BundleInfo(id, uri, type) } } } @@ -463,7 +463,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * resources which should be fetched from the host app class-path. * @param files Files to load as file-system bundles. */ - @Suppress("unused") internal data class Builder ( + @Suppress("unused") public data class Builder ( override var deferred: Boolean = Settings.DEFAULT_DEFERRED_READS, override var readOnly: Boolean = true, override var caseSensitive: Boolean = true, @@ -480,7 +480,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( internal var file: URI? = null, ) : VFSBuilder { /** Factory for embedded VFS implementations. */ - companion object Factory : VFSBuilderFactory { + public companion object Factory : VFSBuilderFactory { override fun newBuilder(): Builder = Builder() override fun newBuilder(builder: Builder): Builder = builder.copy() } @@ -494,7 +494,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @param bundle [FilesystemInfo] and [FileSystem] pair to use as the bundle. * @return This builder. */ - fun setBundle(bundle: Pair): VFSBuilder { + public fun setBundle(bundle: Pair): VFSBuilder { this.file = null this.zip = null this.bundle = bundle @@ -512,7 +512,9 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @param bundle [FilesystemInfo] and [FileSystem] pair to use as the bundle. * @return This builder. */ - fun setBundle(bundle: Triple>): VFSBuilder { + public fun setBundle( + bundle: Triple> + ): VFSBuilder { setBundle( bundle.first to bundle.second ) @@ -532,7 +534,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @param paths URI to the bundle file to load. * @return This builder. */ - fun setBundlePaths(paths: List): VFSBuilder { + public fun setBundlePaths(paths: List): VFSBuilder { this.file = null this.zip = null this.paths = paths @@ -551,7 +553,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @param files File to load bundle data from. * @return This builder. */ - fun setBundleFiles(files: List): VFSBuilder { + public fun setBundleFiles(files: List): VFSBuilder { this.file = null this.zip = null this.files = files @@ -569,7 +571,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @see target Target zip file to use. * @return This builder. */ - fun setZipTarget(target: URI): VFSBuilder { + public fun setZipTarget(target: URI): VFSBuilder { this.file = null this.zip = target this.bundle = null @@ -587,7 +589,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @see target Target zip file to use. * @return This builder. */ - fun setFileTarget(target: URI): VFSBuilder { + public fun setFileTarget(target: URI): VFSBuilder { this.file = target this.zip = null this.bundle = null @@ -605,7 +607,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @see target Target zip file to use. * @return This builder. */ - fun setFileTarget(target: Path): VFSBuilder { + public fun setFileTarget(target: Path): VFSBuilder { this.file = target.toUri() this.zip = null this.bundle = null @@ -623,7 +625,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( * @see target Target zip file to use. * @return This builder. */ - fun setFileTarget(target: File): VFSBuilder { + public fun setFileTarget(target: File): VFSBuilder { this.file = target.toPath().toUri() this.zip = null this.bundle = null @@ -662,7 +664,7 @@ internal class EmbeddedGuestVFSImpl private constructor ( } /** Factory to create new embedded VFS implementations. */ - companion object EmbeddedVFSFactory : VFSFactory { + public companion object EmbeddedVFSFactory : VFSFactory { /** Default in-memory filesystem features. */ private val defaultFeatures = listOf( Feature.FILE_CHANNEL, diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/js/JavaScript.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/js/JavaScript.kt index 63320ad699..bfd3e394a8 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/js/JavaScript.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/js/JavaScript.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -12,6 +12,9 @@ */ package elide.runtime.gvm.js +import com.oracle.truffle.js.runtime.objects.JSDynamicObject +import com.oracle.truffle.js.runtime.objects.Null +import com.oracle.truffle.js.runtime.objects.Undefined import io.micronaut.http.HttpRequest import java.io.InputStream import java.io.Reader @@ -105,3 +108,17 @@ public object JavaScript { } } } + +/** + * ### JavaScript: Undefined + * + * Returns the singleton instance describing JavaScript's `undefined` value. + */ +public fun undefined(): JSDynamicObject = Undefined.instance + +/** + * ### JavaScript: Null + * + * Returns the singleton instance describing JavaScript's `null` value. + */ +public fun nullvalue(): JSDynamicObject = Null.instance diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/AssertAPI.kt b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/AssertAPI.kt index 7cd9064ff1..a17eb9fd10 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/AssertAPI.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/AssertAPI.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Elide Technologies, Inc. + * Copyright (c) 2024-2025 Elide Technologies, Inc. * * Licensed under the MIT license (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -29,10 +29,10 @@ import elide.vm.annotations.Polyglot * `message` parameter. If the `message` parameter is undefined, a default error message is assigned. If the `message` * parameter is an instance of an `Error` then it will be thrown as the error. * - * @param value The value to test. - * @param message The message to display on error. + * @param values The first value is the value to test; the second, if any, is used as a string error message, thrown + * if the first value does not test as truthy. */ - @Polyglot public fun ok(value: Any?, message: Any? = null) + @Polyglot public fun ok(vararg values: Any?) /** * ## Assert: `notOk(value, message)` diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/asserts/NodeAssert.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/asserts/NodeAssert.kt index 94d3e96c50..efb48fb522 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/asserts/NodeAssert.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/asserts/NodeAssert.kt @@ -44,18 +44,62 @@ import elide.runtime.intrinsics.js.JsPromise import elide.runtime.intrinsics.js.err.JsException import elide.runtime.intrinsics.js.node.AssertAPI import elide.runtime.intrinsics.js.node.asserts.AssertionError +import elide.runtime.lang.javascript.NodeModuleName +import elide.runtime.lang.javascript.SyntheticJSModule +import elide.runtime.lang.javascript.SyntheticJSModule.ExportedSymbol import elide.vm.annotations.Polyglot // Symbol where the internal module implementation is installed. -private const val ASSERT_MODULE_SYMBOL: String = "node_assert" +private const val ASSERT_MODULE_SYMBOL: String = "node_${NodeModuleName.ASSERT}" // Symbol where the assertion error type is installed. private const val ASSERTION_ERROR_SYMBOL: String = "AssertionError" +private const val METHOD_OK = "ok" +private const val METHOD_NOT_OK = "notOk" +private const val METHOD_FAIL = "fail" +private const val METHOD_ASSERT = "assert" +private const val METHOD_EQUAL = "equal" +private const val METHOD_STRICT = "strict" +private const val METHOD_NOT_EQUAL = "notEqual" +private const val METHOD_DEEP_EQUAL = "deepEqual" +private const val METHOD_NOT_DEEP_EQUAL = "notDeepEqual" +private const val METHOD_DEEP_STRICT_EQUAL = "deepStrictEqual" +private const val METHOD_NOT_DEEP_STRICT_EQUAL = "notDeepStrictEqual" +private const val METHOD_MATCH = "match" +private const val METHOD_DOES_NOT_MATCH = "doesNotMatch" +private const val METHOD_THROWS = "throws" +private const val METHOD_DOES_NOT_THROW = "doesNotThrow" +private const val METHOD_REJECTS = "rejects" +private const val METHOD_DOES_NOT_REJECT = "doesNotReject" + +// Methods provided by the Node assert module. +private val assertionModuleMethods = arrayOf( + METHOD_OK, + METHOD_NOT_OK, + METHOD_FAIL, + METHOD_ASSERT, + METHOD_EQUAL, + METHOD_STRICT, + METHOD_NOT_EQUAL, + METHOD_DEEP_EQUAL, + METHOD_NOT_DEEP_EQUAL, + METHOD_DEEP_STRICT_EQUAL, + METHOD_NOT_DEEP_STRICT_EQUAL, + METHOD_MATCH, + METHOD_DOES_NOT_MATCH, + METHOD_THROWS, + METHOD_DOES_NOT_THROW, + METHOD_REJECTS, + METHOD_DOES_NOT_REJECT, +) + + // Installs the Node assert module into the intrinsic bindings. @Intrinsic -@Factory internal class NodeAssertModule : AbstractNodeBuiltinModule() { - @Singleton fun provide(): AssertAPI = NodeAssert.obtain() +@Factory internal class NodeAssertModule : SyntheticJSModule, AbstractNodeBuiltinModule() { + private val instance = NodeAssert.obtain() + @Singleton override fun provide(): AssertAPI = instance override fun install(bindings: MutableIntrinsicBindings) { bindings[ASSERT_MODULE_SYMBOL.asJsSymbol()] = provide() @@ -67,6 +111,12 @@ private const val ASSERTION_ERROR_SYMBOL: String = "AssertionError" } } } + + override fun exports(): Array = assertionModuleMethods.map { + ExportedSymbol.of(it) + }.plus( + ExportedSymbol.default(METHOD_ASSERT), + ).toTypedArray() } // Implements Node's `AssertionError` type, which is thrown for assertion failures. @@ -229,8 +279,8 @@ internal class NodeAssert : AssertAPI { } } - @Polyglot override fun ok(value: Any?, message: Any?) { - checkTruthy(false, value, message) + @Polyglot override fun ok(vararg values: Any?) { + checkTruthy(false, values.firstOrNull(), values.getOrNull(2)) } @Polyglot override fun notOk(value: Any?, message: Any?) { diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/childProcess/NodeChildProcess.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/childProcess/NodeChildProcess.kt index 575e1a36c6..b3ebe970c1 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/childProcess/NodeChildProcess.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/childProcess/NodeChildProcess.kt @@ -61,14 +61,18 @@ import elide.runtime.intrinsics.js.node.events.EventEmitter import elide.runtime.intrinsics.js.node.events.EventTarget import elide.runtime.intrinsics.js.node.fs.StringOrBuffer import elide.runtime.intrinsics.js.node.path.Path +import elide.runtime.lang.javascript.SyntheticJSModule import elide.runtime.node.events.EventAware import elide.runtime.node.fs.NodeFilesystemModule import elide.runtime.node.fs.resolveEncodingString import elide.vm.annotations.Polyglot import com.google.common.util.concurrent.ListenableFuture as Future +// Name of the child process module. +private const val CHILD_PROCESS_MODULE_NAME = "child_process" + // Internal symbol where the Node built-in module is installed. -private const val CHILD_PROCESS_MODULE_SYMBOL = "node_child_process" +private const val CHILD_PROCESS_MODULE_SYMBOL = "node_${CHILD_PROCESS_MODULE_NAME}" // Internal symbol where the Node `ChildProcess` class is installed. private const val CHILD_PROCESS_CLASS_SYMBOL = "NodeChildProcess" @@ -91,22 +95,23 @@ private val globalIpcServer by lazy { InterElideIPCServer() } // Installs the Node child process module into the intrinsic bindings. @Intrinsic @Factory internal class NodeChildProcessModule( - private val filesystem: Optional>, - private val executorProvider: GuestExecutorProvider, -) : AbstractNodeBuiltinModule() { + filesystem: NodeFilesystemModule, + executorProvider: GuestExecutorProvider, +) : SyntheticJSModule, AbstractNodeBuiltinModule() { private val defaultStandardStreams = object : StandardStreamsProvider { override fun stdin(): InputStream? = System.`in` override fun stdout(): OutputStream? = System.out override fun stderr(): OutputStream? = System.err } - @Singleton internal fun provide(): ChildProcessAPI = NodeChildProcess.obtain( + private val instance = NodeChildProcess.obtain( { globalIpcServer }, filesystem, executorProvider, defaultStandardStreams, ) + @Singleton override fun provide(): ChildProcessAPI = instance @Singleton internal fun ipcServer(): InterElideIPCServer = globalIpcServer override fun install(bindings: MutableIntrinsicBindings) { @@ -1083,14 +1088,14 @@ internal class NodeChildProcess( /** @return Singleton instance of the [NodeChildProcess] module; it is initialized if not yet available. */ internal fun obtain( ipcSupplier: Supplier, - fs: Optional>, + fs: NodeFilesystemModule, executorProvider: GuestExecutorProvider, standardStreams: StandardStreamsProvider, ): NodeChildProcess = SINGLETON.get().let { when (it) { null -> NodeChildProcess( ipcSupplier, - fs, + Optional.of(Supplier { fs }), standardStreams, executorProvider, ).also { childProc -> SINGLETON.set(childProc) } diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/fs/NodeFilesystem.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/fs/NodeFilesystem.kt index b959901f68..d2c5ecb96f 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/fs/NodeFilesystem.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/fs/NodeFilesystem.kt @@ -42,6 +42,8 @@ import elide.runtime.gvm.internals.intrinsics.js.AbstractNodeBuiltinModule import elide.runtime.gvm.internals.intrinsics.js.JsPromiseImpl.Companion.spawn import elide.runtime.gvm.js.JsError import elide.runtime.gvm.js.JsSymbol.JsSymbols.asJsSymbol +import elide.runtime.gvm.loader.ModuleInfo +import elide.runtime.gvm.loader.ModuleRegistry import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings import elide.runtime.intrinsics.js.JsPromise import elide.runtime.intrinsics.js.err.AbstractJsException @@ -51,6 +53,8 @@ import elide.runtime.intrinsics.js.node.* import elide.runtime.intrinsics.js.node.buffer.BufferInstance import elide.runtime.intrinsics.js.node.fs.* import elide.runtime.intrinsics.js.node.path.Path +import elide.runtime.lang.javascript.NodeModuleName +import elide.runtime.lang.javascript.SyntheticJSModule import elide.runtime.node.path.NodePathsModule import elide.runtime.plugins.vfs.VfsListener import elide.runtime.vfs.GuestVFS @@ -59,6 +63,9 @@ import elide.vm.annotations.Polyglot // Default copy mode value. private const val DEFAULT_COPY_MODE: Int = 0 +// Whether to enable synthesized modules. +private const val ENABLE_SYNTHESIZED: Boolean = false + // Listener bean for VFS init. @Singleton @Eager public class VfsInitializerListener : VfsListener, Supplier { private val filesystem: AtomicReference = AtomicReference() @@ -72,7 +79,7 @@ private const val DEFAULT_COPY_MODE: Int = 0 // Installs the Node `fs` and `fs/promises` modules into the intrinsic bindings. @Intrinsic -@Factory internal class NodeFilesystemModule( +@Factory @Singleton internal class NodeFilesystemModule( private val pathModule: NodePathsModule, private val vfs: VfsInitializerListener, private val executorProvider: GuestExecutorProvider, @@ -92,6 +99,23 @@ private const val DEFAULT_COPY_MODE: Int = 0 bindings[NodeFilesystem.SYMBOL_STD.asJsSymbol()] = ProxyExecutable { provideStd() } bindings[NodeFilesystem.SYMBOL_PROMISES.asJsSymbol()] = ProxyExecutable { providePromises() } } + + inner class FilesystemApiShim: SyntheticJSModule { + override fun provide(): FilesystemAPI = std + } + + inner class FilesystemPromisesApiShim : SyntheticJSModule { + override fun provide(): FilesystemPromiseAPI = promises + } + + init { + if (ENABLE_SYNTHESIZED) { + val apiShim = FilesystemApiShim() + val promisesShim = FilesystemPromisesApiShim() + ModuleRegistry.deferred(ModuleInfo.of(NodeModuleName.FS)) { apiShim } + ModuleRegistry.deferred(ModuleInfo.of(NodeModuleName.FS_PROMISES)) { promisesShim } + } + } } /** @@ -149,7 +173,7 @@ internal object FilesystemConstants { } // Context for a file write operation with managed state. -@OptIn(DelicateElideApi::class) internal interface FileWriterContext { +internal interface FileWriterContext { companion object { @JvmStatic fun of(path: java.nio.file.Path, encoding: Charset?, channel: WritableByteChannel): FileWriterContext { return object : FileWriterContext { diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/os/NodeOperatingSystem.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/os/NodeOperatingSystem.kt index 35300c5640..09ca8fa65c 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/os/NodeOperatingSystem.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/os/NodeOperatingSystem.kt @@ -31,15 +31,24 @@ import elide.runtime.gvm.api.Intrinsic import elide.runtime.gvm.internals.intrinsics.js.AbstractNodeBuiltinModule import elide.runtime.gvm.js.JsError import elide.runtime.gvm.js.JsSymbol.JsSymbols.asJsSymbol +import elide.runtime.gvm.loader.ModuleInfo +import elide.runtime.gvm.loader.ModuleRegistry import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings import elide.runtime.intrinsics.js.node.OperatingSystemAPI import elide.runtime.intrinsics.js.node.fs.StringOrBuffer import elide.runtime.intrinsics.js.node.os.* import elide.runtime.intrinsics.js.node.os.OSType.POSIX import elide.runtime.intrinsics.js.node.os.OSType.WIN32 +import elide.runtime.lang.javascript.SyntheticJSModule import elide.runtime.node.childProcess.ChildProcessNative import elide.vm.annotations.Polyglot +// Name of this module. +internal const val NODE_OS_NAME: String = "os" + +// Original primordial symbol for this module. +internal const val NODE_OS_SYMBOL: String = "node_${NODE_OS_NAME}" + // Constants for cross-OS values. private const val win32EOL: String = "\r\n" private const val posixEOL: String = "\n" @@ -184,13 +193,16 @@ private val moduleMembers = arrayOf( // Installs the Node OS module into the intrinsic bindings. @Intrinsic -@Factory internal class NodeOperatingSystemModule : AbstractNodeBuiltinModule() { +@Factory internal class NodeOperatingSystemModule : SyntheticJSModule, AbstractNodeBuiltinModule() { // Provide a compliant instance of the OS API to the DI context. - @Singleton fun provide(): OperatingSystemAPI = NodeOperatingSystem.obtain() + @Singleton override fun provide(): OperatingSystemAPI = NodeOperatingSystem.obtain() override fun install(bindings: MutableIntrinsicBindings) { - bindings[NodeOperatingSystem.SYMBOL.asJsSymbol()] = - NodeOperatingSystem.obtain() + bindings[NODE_OS_SYMBOL.asJsSymbol()] = NodeOperatingSystem.obtain() + } + + init { + ModuleRegistry.deferred(ModuleInfo.of(NODE_OS_NAME)) { NodeOperatingSystem.obtain() } } } @@ -198,9 +210,6 @@ private val moduleMembers = arrayOf( * # Node API: `os` */ internal object NodeOperatingSystem { - /** Primordial symbol where the OS API implementation is installed. */ - internal const val SYMBOL: String = "node_os" - internal abstract class ModuleBase : ProxyObject, OperatingSystemAPI { override fun getMemberKeys(): Array = moduleMembers override fun hasMember(key: String): Boolean = key in moduleMembers diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/path/NodePaths.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/path/NodePaths.kt index 667c4d3622..5e418ef751 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/path/NodePaths.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/path/NodePaths.kt @@ -18,13 +18,18 @@ import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyExecutable import org.graalvm.polyglot.proxy.ProxyObject import java.io.File +import elide.annotations.Factory +import elide.annotations.Singleton import elide.runtime.gvm.api.Intrinsic import elide.runtime.gvm.internals.intrinsics.js.AbstractNodeBuiltinModule import elide.runtime.gvm.js.JsSymbol.JsSymbols.asJsSymbol +import elide.runtime.gvm.loader.ModuleInfo +import elide.runtime.gvm.loader.ModuleRegistry import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings import elide.runtime.intrinsics.js.node.PathAPI import elide.runtime.intrinsics.js.node.path.Path import elide.runtime.intrinsics.js.node.path.PathFactory +import elide.runtime.lang.javascript.SyntheticJSModule import elide.runtime.node.path.NodePaths.SYMBOL import elide.runtime.node.path.PathStyle.POSIX import elide.runtime.node.path.PathStyle.WIN32 @@ -235,15 +240,23 @@ public class PathBuf private constructor( } // Installs the Node paths module into the intrinsic bindings. -@Intrinsic -internal class NodePathsModule : AbstractNodeBuiltinModule() { - private val instance = NodePaths.create() +@Intrinsic @Factory internal class NodePathsModule : SyntheticJSModule, AbstractNodeBuiltinModule() { + companion object { + // Singleton instance. + private val instance = NodePaths.create() + + init { + ModuleRegistry.deferred(ModuleInfo.of("path")) { instance } + } + } val paths: PathAPI get() = instance override fun install(bindings: MutableIntrinsicBindings) { bindings[SYMBOL.asJsSymbol()] = NodePaths.create() } + + @Singleton override fun provide(): PathAPI = instance } /** diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/zlib/NodeZlib.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/zlib/NodeZlib.kt index 77b002c948..2cdb74e409 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/zlib/NodeZlib.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/zlib/NodeZlib.kt @@ -44,6 +44,7 @@ import elide.runtime.intrinsics.js.node.ZlibBuffer import elide.runtime.intrinsics.js.node.stream.Readable import elide.runtime.intrinsics.js.node.stream.Writable import elide.runtime.intrinsics.js.node.zlib.* +import elide.runtime.lang.javascript.SyntheticJSModule import elide.runtime.node.stream.AbstractReadable import elide.runtime.node.stream.ColdInputStream import elide.runtime.node.stream.WrappedInputStream @@ -430,10 +431,9 @@ public data object ModernNodeZlibConstants : NodeZlibConstants { } // Installs the Node zlib module into the intrinsic bindings. -@Intrinsic -@Factory internal class NodeZlibModule : AbstractNodeBuiltinModule() { +@Intrinsic internal class NodeZlibModule : SyntheticJSModule, AbstractNodeBuiltinModule() { @Inject private lateinit var zlib: NodeZlib - @Singleton internal fun provide(): ZlibAPI = zlib + override fun provide(): NodeZlib = zlib override fun install(bindings: MutableIntrinsicBindings) { bindings[ZLIB_MODULE_SYMBOL.asJsSymbol()] = provide() diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt index 44bbc81602..7a02ea3d80 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt @@ -12,6 +12,7 @@ */ package elide.runtime.plugins.js +import com.oracle.truffle.js.runtime.JSContextOptions import org.graalvm.polyglot.Engine import org.graalvm.polyglot.Source import java.util.concurrent.atomic.AtomicBoolean @@ -27,6 +28,7 @@ import elide.runtime.core.extensions.disableOptions import elide.runtime.core.extensions.enableOptions import elide.runtime.core.extensions.setOptions import elide.runtime.core.plugin +import elide.runtime.lang.javascript.JavaScriptLang import elide.runtime.plugins.AbstractLanguagePlugin import elide.runtime.plugins.AbstractLanguagePlugin.LanguagePluginManifest import elide.runtime.plugins.env.Environment @@ -157,6 +159,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.* "js.unhandled-rejections" to UNHANDLED_REJECTIONS, // Experimental: "js.charset" to config.charset.name(), + "js.module-loader-factory" to JSContextOptions.ModuleLoaderFactoryMode.HANDLER.toString(), ) setOptions( @@ -196,7 +199,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.* private const val JS_LANGUAGE_ID = "js" private const val JS_PLUGIN_ID = "JavaScript" - private const val EXPERIMENTAL_MODULE_PRELOADING = true + private const val EXPERIMENTAL_MODULE_PRELOADING = false private const val WASI_STD = "wasi_snapshot_preview1" private const val FUNCTION_CONSTRUCTOR_CACHE_SIZE: String = "256" @@ -208,6 +211,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.* override val key: Key = Key(JS_PLUGIN_ID) override fun install(scope: InstallationScope, configuration: JavaScriptConfig.() -> Unit): JavaScript { + JavaScriptLang.initialize() configureLanguageSupport(scope) // resolve the env plugin (if present, otherwise ignore silently) diff --git a/packages/graalvm/src/test/kotlin/elide/runtime/node/NodePathTest.kt b/packages/graalvm/src/test/kotlin/elide/runtime/node/NodePathTest.kt index 7a4fe183d6..83ebe955fc 100644 --- a/packages/graalvm/src/test/kotlin/elide/runtime/node/NodePathTest.kt +++ b/packages/graalvm/src/test/kotlin/elide/runtime/node/NodePathTest.kt @@ -464,8 +464,8 @@ fun PathAPI.testExtname(parsed: PathIntrinsic): String? { @Test fun `parsing paths should work from javascript`() = executeGuest { // language=javascript """ - const { equal } = require("assert"); const { parse } = require("path"); + const { equal } = require("assert"); const parsed1 = parse("/sample/cool/path"); equal(parsed1.dir, '/sample/cool'); equal(parsed1.base, 'path'); diff --git a/settings.gradle.kts b/settings.gradle.kts index 6dde2da9a6..83a65a5e46 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -217,6 +217,7 @@ include( ":packages:graalvm", ":packages:graalvm-java", ":packages:graalvm-jvm", + ":packages:graalvm-js", ":packages:graalvm-kt", ":packages:graalvm-llvm", ":packages:graalvm-py", diff --git a/third_party/oracle/graaljs.jar b/third_party/oracle/graaljs.jar new file mode 100644 index 0000000000..41cae51696 Binary files /dev/null and b/third_party/oracle/graaljs.jar differ diff --git a/third_party/oracle/graalvm/graal b/third_party/oracle/graalvm/graal new file mode 160000 index 0000000000..804dab21e1 --- /dev/null +++ b/third_party/oracle/graalvm/graal @@ -0,0 +1 @@ +Subproject commit 804dab21e1ebb363172794e9be20b5ea070d807d diff --git a/third_party/oracle/graalvm/graaljs b/third_party/oracle/graalvm/graaljs new file mode 160000 index 0000000000..992edb8044 --- /dev/null +++ b/third_party/oracle/graalvm/graaljs @@ -0,0 +1 @@ +Subproject commit 992edb8044fce8761adcb816420e1ec2e0649cab diff --git a/third_party/oracle/truffle-js-snapshot-tool.jar b/third_party/oracle/truffle-js-snapshot-tool.jar new file mode 100644 index 0000000000..357741c043 Binary files /dev/null and b/third_party/oracle/truffle-js-snapshot-tool.jar differ diff --git a/tools/scripts/paths.cjs b/tools/scripts/paths.cjs new file mode 100644 index 0000000000..3570658555 --- /dev/null +++ b/tools/scripts/paths.cjs @@ -0,0 +1,6 @@ +const { join, resolve } = require("node:path") + +const cwd = process.cwd() +const this_plus_dev = join(cwd, ".dev", "cool", "..") +const actually_just_dev = resolve(this_plus_dev) +console.log("path: ", actually_just_dev) diff --git a/tools/scripts/paths.mjs b/tools/scripts/paths.mjs new file mode 100644 index 0000000000..040d0d3278 --- /dev/null +++ b/tools/scripts/paths.mjs @@ -0,0 +1,6 @@ +import { join, resolve } from "node:path" + +const cwd = process.cwd() +const this_plus_dev = join(cwd, ".dev", "cool", "..") +const actually_just_dev = resolve(this_plus_dev) +console.log("path: ", actually_just_dev)