Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: synthesized modules #1220

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ val enabledSecurityProviders = listOfNotNull(

val preinitializedContexts = if (!enablePreinit) emptyList() else listOfNotNull(
"js",
"ejs", // ElideJS
onlyIf(enablePreinitializeAll && enableRuby, "ruby"),
onlyIf(enablePreinitializeAll && enablePython, "python"),
onlyIf(enablePreinitializeAll && enableJvm, "java"),
Expand Down Expand Up @@ -884,7 +885,7 @@ val releaseFlags: List<String> = listOf(
if (oracleGvm) gvmReleaseFlags else emptyList(),
).flatten()).toList()

val jvmDefs = mapOf(
val jvmDefs = mutableMapOf(
"elide.strict" to "true",
"elide.natives" to nativesPath,
"elide.target" to targetPath.asFile.path,
Expand Down Expand Up @@ -916,6 +917,10 @@ val jvmDefs = mapOf(
// "java.util.concurrent.ForkJoinPool.common.exceptionHandler" to "",
)

findProperty("elide.logLevel")?.let {
jvmDefs["elide.logging.root.level"] = it as String
}

val hostedRuntimeOptions = mapOf(
"IncludeLocales" to "en",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ import org.graalvm.polyglot.Engine as VMEngine
logging.trace("Language '${it.name}' is supported")
supported to it
}
}.flatMap { (guest, lang) ->
}.distinctBy { it.first.id }.flatMap { (guest, lang) ->
listOf(guest to lang) + additionalEnginesByLang[guest]?.map { additional ->
additional to lang
}.orEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
package elide.tool.cli

/** GraalVM engine name for GraalJs. */
const val ENGINE_JS = "js"
const val ENGINE_JS = "ejs"

/** GraalVM engine name for GraalPython. */
const val ENGINE_PYTHON = "python"
Expand All @@ -28,5 +28,8 @@ const val ENGINE_JVM = "java"
/** GraalVM engine name for GraalWasm. */
const val ENGINE_WASM = "wasm"

/** GraalVM engine name for Pkl. */
const val ENGINE_PKL = "pkl"

/** GraalVM engine name for GraalLLVM. */
const val ENGINE_LLVM = "llvm"
22 changes: 19 additions & 3 deletions packages/cli/src/main/kotlin/elide/tool/cli/GuestLanguage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

package elide.tool.cli

import elide.runtime.gvm.internals.js.ELIDE_JS_LANGUAGE_ID

/** Specifies languages supported for REPL access. */
enum class GuestLanguage (
internal val id: String,
Expand All @@ -28,6 +30,7 @@ enum class GuestLanguage (
internal val dependsOn: List<GuestLanguage> = emptyList(),
internal val executionMode: ExecutionMode = ExecutionMode.SOURCE_DIRECT,
internal val secondary: Boolean = dependsOn.isNotEmpty(),
override val requestId: String = engine,
) : elide.runtime.gvm.GuestLanguage, elide.runtime.core.GuestLanguage {
/** Interactive JavaScript VM. */
JS (
Expand All @@ -37,6 +40,7 @@ enum class GuestLanguage (
onByDefault = true,
extensions = listOf("js", "cjs", "mjs"),
mimeTypes = listOf("application/javascript", "application/javascript+module", "application/ecmascript"),
requestId = ELIDE_JS_LANGUAGE_ID,
),

/** JavaScript VM enabled with TypeScript support. */
Expand Down Expand Up @@ -132,37 +136,49 @@ enum class GuestLanguage (
dependsOn = listOf(JVM),
),

/** Interactive nested JVM. */
/** WebAssembly. */
WASM (
id = ENGINE_WASM,
formalName = "WASM",
experimental = true,
suppressExperimentalWarning = true,
extensions = listOf("wasm"),
mimeTypes = listOf("application/wasm"),
),

/** Apple Pkl. */
PKL (
id = ENGINE_PKL,
formalName = "Pkl",
experimental = true,
suppressExperimentalWarning = true,
extensions = listOf("pkl"),
mimeTypes = listOf("application/pkl"),
);

companion object {
/** @return Language based on the provided ID, or `null`. */
internal fun resolveFromEngine(id: String): GuestLanguage? = when (id) {
JS.engine -> JS
JS.engine, ELIDE_JS_LANGUAGE_ID -> JS
TYPESCRIPT.engine -> TYPESCRIPT
PYTHON.engine -> PYTHON
RUBY.engine -> RUBY
JVM.engine -> JVM
WASM.engine -> WASM
LLVM.engine -> LLVM
PKL.engine -> PKL
else -> null
}

/** @return Language based on the provided ID, or `null`. */
internal fun resolveFromId(id: String): GuestLanguage? = when (id) {
JS.id -> JS
JS.id, ELIDE_JS_LANGUAGE_ID -> JS
PYTHON.id -> PYTHON
RUBY.id -> RUBY
JVM.id -> JVM
WASM.id -> WASM
LLVM.id -> LLVM
PKL.id -> PKL
TYPESCRIPT.id -> TYPESCRIPT

// JVM extension guests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ abstract class AbstractScriptEngineFactory protected constructor (val engine: Gr
private val polyglotEngine = Engine.newBuilder().build()
}

private val languageId: String = engine.engine
private val languageId: String = engine.requestId

private val language by lazy {
requireNotNull(polyglotEngine.languages[languageId]) {
"Failed to resolve language implementation with ID $languageId"
Expand Down Expand Up @@ -236,7 +237,7 @@ abstract class AbstractScriptEngineFactory protected constructor (val engine: Gr
}
}

private class PolyglotContext (private val languageId: String) : ScriptContext {
private class PolyglotContext constructor (private val languageId: String) : ScriptContext {
private var context: Context? = null
private val `in`: PolyglotReader
private val out: PolyglotWriter
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/main/resources/elide.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
elide:
gvm:
enabled: true
languages: ["JS"]
languages: ["EJS"]
defer: true
js:
enabled: true
Expand Down
1 change: 1 addition & 0 deletions packages/embedded/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ val sharedLibArgs = sequenceOf(
"-J-Dpolyglot.image-build-time.PreinitializeContextsWithNative=true",
"-J-Dpolyglot.image-build-time.PreinitializeContexts=" + listOfNotNull(
"js",
"ejs",
).joinToString(","),
)

Expand Down
12 changes: 12 additions & 0 deletions packages/engine/api/engine.api
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ public final class elide/runtime/gvm/cfg/LanguageDefaults {
public final fun getDEFAULT_TIMEZONE ()Ljava/time/ZoneId;
}

public final class elide/runtime/gvm/loader/JSRealmPatcher {
public static final field INSTANCE Lelide/runtime/gvm/loader/JSRealmPatcher;
public final fun getModuleLoaderField ()Ljava/lang/reflect/Field;
}

public final class elide/runtime/gvm/loader/LoaderRegistry {
public static final field INSTANCE Lelide/runtime/gvm/loader/LoaderRegistry;
public static final fun mountPrimary (Lcom/oracle/truffle/js/runtime/JSRealm;Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader;)V
public static final fun register (Lcom/oracle/truffle/js/runtime/JSRealm;Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader;)V
public static final fun register (Lcom/oracle/truffle/js/runtime/objects/JSModuleLoader;)V
}

public abstract class elide/runtime/plugins/AbstractLanguageConfig {
public static final field Companion Lelide/runtime/plugins/AbstractLanguageConfig$Companion;
public fun <init> ()V
Expand Down
1 change: 1 addition & 0 deletions packages/engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
api(libs.graalvm.polyglot)
api(libs.kotlinx.coroutines.core)
api(libs.guava)
api(libs.graalvm.js.language)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 com.oracle.truffle.js.builtins.commonjs.NpmCompatibleESModuleLoader
import com.oracle.truffle.js.runtime.JSRealm
import com.oracle.truffle.js.runtime.objects.JSModuleLoader
import java.lang.reflect.Field

/**
* # JS Realm Patcher
*
* Internal utilities for patching the root JavaScript realm.
*/
public object JSRealmPatcher {
// Resolved field for the root JS realm's module loader.
@PublishedApi internal val moduleLoaderField: Field by lazy {
try {
JSRealm::class.java.getDeclaredField("moduleLoader").also {
it.isAccessible = true
}
} catch (e: NoSuchFieldException) {
throw IllegalStateException("Failed to resolve `moduleLoader` field from `JSRealm`", e)
} catch (e: IllegalAccessException) {
throw IllegalStateException("Failed to forcibly allow access to `moduleLoader` field from `JSRealm`", e)
}
}

/**
* Override the current [JSModuleLoader] with a wrapped instance, based on the [Base] loader type; the returned
* [Loader] type is the overridden loader, which is expected to wrap the base loader.
*
* Under the hood, this method simply calls [getModuleLoader], then the [prepare] factory, then [installModuleLoader].
*
* @param jsRealm The JS realm to override the module loader in.
* @param prepare The factory to prepare the new loader instance.
* @return The overridden loader instance.
*/
public inline fun <reified Base : JSModuleLoader, reified Loader : JSModuleLoader> overrideLoader(
jsRealm: JSRealm,
crossinline prepare: (Base) -> Loader,
): Loader = getModuleLoader<Base>(jsRealm).let { baseLoader ->
prepare(baseLoader).also { loader ->
installModuleLoader(jsRealm, loader)
}
}

/**
* Retrieve the current [JSModuleLoader] from the provided [jsRealm].
*
* This method will throw if:
* - The loader is not available because its field is set to `null`.
* - The loader is not an instance of the expected type.
*
* @param jsRealm The JS realm to retrieve the module loader from.
* @return The module loader instance.
* @throws IllegalStateException If the module loader is not available or is not an instance of the expected type.
*/
public inline fun <reified Loader : JSModuleLoader> getModuleLoader(jsRealm: JSRealm): Loader {
return (moduleLoaderField[jsRealm] ?: NpmCompatibleESModuleLoader.create(jsRealm)).also {
require(it is Loader) {
"Failed to cast `moduleLoader` field from `JSRealm` to `${Loader::class.java.simpleName}`"
}
} as Loader
}

/**
* Forcibly install the specified [moduleLoader] from the provided [jsRealm].
*
* This method will throw if:
* - The field cannot be resolved (on first access).
* - The loader cannot be set because its field is not accessible.
*
* @param jsRealm The JS realm to install the module loader into.
* @throws IllegalStateException If the module loader install step fails.
*/
public inline fun <reified Loader : JSModuleLoader> installModuleLoader(jsRealm: JSRealm, moduleLoader: Loader,) {
val current = moduleLoaderField[jsRealm]
if (current !== moduleLoader) {
moduleLoaderField[jsRealm] = moduleLoader
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 com.oracle.truffle.js.runtime.JSRealm
import com.oracle.truffle.js.runtime.objects.JSModuleLoader
import java.lang.ref.WeakReference
import java.util.LinkedList
import java.util.concurrent.ConcurrentHashMap

/**
* # Loader Registry
*
* Static utility for registering [JSModuleLoader]-compliant implementations, and similar objects for other languages;
* these registered objects are consulted from delegated Elide types.
*/
public object LoaderRegistry {
// Registered JavaScript module loaders.
@JvmStatic private val rootEsLoaders = LinkedList<JSModuleLoader>()

// Registered JavaScript Realm-specific module loaders.
@JvmStatic private val realmBoundEsLoaders = ConcurrentHashMap<JSRealm, LinkedList<WeakReference<JSModuleLoader>>>()

/**
* Register a JavaScript module loader.
*
* @param loader The JavaScript module loader to register.
*/
@JvmStatic public fun register(loader: JSModuleLoader) {
rootEsLoaders.add(loader)
}

/**
* Register a JavaScript module loader.
*
* @param realm JavaScript realm to bind this loader to.
* @param loader The JavaScript module loader to register.
*/
@JvmStatic public fun register(realm: JSRealm, loader: JSModuleLoader) {
val bound = realmBoundEsLoaders.computeIfAbsent(realm) { LinkedList() }
bound.add(WeakReference(loader))
}

/**
* Mount the provided module [loader] forcibly as the main loader for a given JavaScript [realm].
*
* @param realm The JavaScript realm to mount the loader to.
* @param loader The JavaScript module loader to mount.
*/
@JvmStatic public fun mountPrimary(realm: JSRealm, loader: JSModuleLoader) {
if (realm.parent == null) {
register(loader)
}
register(realm, loader) // associate with realm, too
JSRealmPatcher.overrideLoader<JSModuleLoader, JSModuleLoader>(realm) {
loader
}
}
}
Loading
Loading