Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
72b0b82
feat(graalvm-php): initial structure for trufflephp
sgammon Oct 31, 2025
90e8832
feat(graalvm-php): phase 1a - increment/decrement, break/continue
sgammon Oct 31, 2025
3d75dbe
feat(graalvm-php): phase 1c - php builtins
sgammon Oct 31, 2025
8297dfb
feat(graalvm-php): phase 1d - object/class support
sgammon Oct 31, 2025
1042ffe
feat(graalvm-php): phase 1e - null coalescing, static members, string…
sgammon Oct 31, 2025
485035e
feat(graalvm-php): phase 1f - class inheritance
sgammon Oct 31, 2025
fafb1a1
feat(graalvm-php): phase 1g - try/catch/finally
sgammon Oct 31, 2025
550498a
feat(graalvm-php): phase 1h - continued array functionality
sgammon Oct 31, 2025
d542dcf
feat(graalvm-php): phase 1i - `parent::` support
sgammon Oct 31, 2025
6a76326
feat(graalvm-php): phase 1j - `self::` support
sgammon Oct 31, 2025
b78e32b
feat(graalvm-php): phase 1k - abstract classes and magic methods
sgammon Oct 31, 2025
e25b610
feat(graalvm-php): phase 1l - continued magic method implementations
sgammon Oct 31, 2025
dde4179
feat(graalvm-php): phase 2a - include and require
sgammon Oct 31, 2025
decc9c7
feat(graalvm-php): refactor parser and tests
sgammon Oct 31, 2025
5655c9e
feat(graalvm-php): null coalescing operator
sgammon Oct 31, 2025
e2f704b
feat(graalvm-php): namespaces support
sgammon Oct 31, 2025
fb4283f
feat(graalvm-php): magic constants/variadic functions/spread operator
sgammon Oct 31, 2025
379d14c
feat(graalvm-php): closures/arrows/anon fns
sgammon Oct 31, 2025
78d1b08
feat(graalvm-php): traits
sgammon Oct 31, 2025
c76a5f7
chore(graalvm-php): update benchmarks
sgammon Nov 1, 2025
10611e7
feat(graalvm-php): finish implementation of traits, refactor for ref …
sgammon Nov 1, 2025
e44f5a6
chore(graalvm-php): fmt
sgammon Nov 1, 2025
ae9ebdf
chore(graalvm-php): refactor to elide package paths
sgammon Nov 1, 2025
4251720
chore(graalvm-php): update benchmarks
sgammon Nov 1, 2025
7ec7b2b
chore(graalvm-php): update module pins
sgammon Nov 1, 2025
14ee509
fix(graalvm-php): package of truffle boundary fixes
sgammon Nov 1, 2025
4aa3a7c
feat(cli): init and enable trufflephp
sgammon Nov 1, 2025
b087515
chore: fmt
sgammon Nov 5, 2025
6b3f5f0
chore: update api pins for `graalvm-php`
sgammon Nov 5, 2025
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
4 changes: 4 additions & 0 deletions packages/cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ val enablePythonDynamic = true
val enableRuby = false
val enableLlvm = true
val enableJvm = true
val enablePhp = true
val enableKotlin = true
val enableSqlite = true
val enableAllLocales = false
Expand Down Expand Up @@ -604,6 +605,9 @@ dependencies {
compileOnly(projects.packages.graalvmJava)
compileOnly(projects.packages.graalvmKt)
}
if (enablePhp) {
implementation(projects.packages.graalvmPhp)
}
}

// GraalVM: Tooling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ const val ENGINE_LLVM = "llvm"

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

/** GraalVM engine name for TrufflePHP. */
const val ENGINE_PHP = "php"
11 changes: 11 additions & 0 deletions packages/cli/src/main/kotlin/elide/tool/cli/GuestLanguage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ enum class GuestLanguage (
extensions = sortedSetOf("rb"),
),

/** Interactive PHP VM. */
PHP (
id = ENGINE_PHP,
formalName = "PHP",
experimental = true,
onByDefault = true,
extensions = sortedSetOf("php"),
),

/** Interactive nested JVM. */
JVM (
id = "jvm",
Expand Down Expand Up @@ -169,6 +178,7 @@ enum class GuestLanguage (
WASM.engine -> WASM
LLVM.engine -> LLVM
PKL.engine -> PKL
PHP.engine -> PHP
else -> null
}

Expand All @@ -182,6 +192,7 @@ enum class GuestLanguage (
LLVM.id -> LLVM
TYPESCRIPT.id -> TYPESCRIPT
PKL.id -> PKL
PHP.id -> PHP

// JVM extension guests
KOTLIN.id -> KOTLIN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,10 @@ internal class ToolShellCommand : ProjectAwareSubcommand<ToolState, CommandConte
resourcesPath = gvmResources
}

PHP -> configure(elide.runtime.plugins.php.PHP) {
logging.debug("Configuring PHP support")
}

// RUBY -> ignoreNotInstalled {
// install(elide.runtime.plugins.ruby.Ruby) {
// logging.debug("Configuring Ruby VM")
Expand Down
1,306 changes: 1,306 additions & 0 deletions packages/graalvm-php/api/graalvm-php.api

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions packages/graalvm-php/benchmark-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# TrufflePHP Benchmark Report

## Benchmark Suite: Micro Benchmarks

### array_associative

**Performance:**
- Mean: 5.19 ms
- Median: 4.77 ms
- Std Dev: 1.33 ms
- Min: 4.00 ms
- Max: 9.19 ms
- Throughput: 192.68 ops/sec

**Memory:**
- Before: 23.91 MB
- After: 49.68 MB
- Delta: 25.77 MB

### array_sequential

**Performance:**
- Mean: 2.07 ms
- Median: 1.11 ms
- Std Dev: 3.20 ms
- Min: 843.98 μs
- Max: 15.68 ms
- Throughput: 482.08 ops/sec

**Memory:**
- Before: 23.59 MB
- After: 30.89 MB
- Delta: 7.30 MB

### property_access

**Performance:**
- Mean: 1.53 ms
- Median: 1.34 ms
- Std Dev: 640.42 μs
- Min: 894.05 μs
- Max: 4.08 ms
- Throughput: 653.23 ops/sec

**Memory:**
- Before: 23.60 MB
- After: 30.56 MB
- Delta: 6.96 MB

### string_concat

**Performance:**
- Mean: 1.27 ms
- Median: 1.02 ms
- Std Dev: 726.07 μs
- Min: 700.67 μs
- Max: 4.11 ms
- Throughput: 786.48 ops/sec

**Memory:**
- Before: 23.60 MB
- After: 38.57 MB
- Delta: 14.97 MB

### arithmetic

**Performance:**
- Mean: 2.55 ms
- Median: 2.22 ms
- Std Dev: 750.88 μs
- Min: 1.88 ms
- Max: 4.56 ms
- Throughput: 392.29 ops/sec

**Memory:**
- Before: 23.60 MB
- After: 24.26 MB
- Delta: 678.36 KB

### method_calls

**Performance:**
- Mean: 2.14 ms
- Median: 1.91 ms
- Std Dev: 990.39 μs
- Min: 1.47 ms
- Max: 6.26 ms
- Throughput: 468.16 ops/sec

**Memory:**
- Before: 23.60 MB
- After: 51.58 MB
- Delta: 27.98 MB



## Summary

| Benchmark | Mean | Median | Std Dev | Min | Max | Throughput |
-----------|------|--------|---------|-----|-----|------------|
array_associative | 5ms | 5ms | 1ms | 4ms | 9ms | 192.68 ops/s |
array_sequential | 2ms | 1ms | 3ms | 844μs | 16ms | 482.08 ops/s |
property_access | 2ms | 1ms | 640μs | 894μs | 4ms | 653.23 ops/s |
string_concat | 1ms | 1ms | 726μs | 701μs | 4ms | 786.48 ops/s |
arithmetic | 3ms | 2ms | 751μs | 2ms | 5ms | 392.29 ops/s |
method_calls | 2ms | 2ms | 990μs | 1ms | 6ms | 468.16 ops/s |
72 changes: 72 additions & 0 deletions packages/graalvm-php/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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.native.NativeTarget
import elide.internal.conventions.publishing.publish

plugins {
kotlin("jvm")
alias(libs.plugins.micronaut.graalvm)
alias(libs.plugins.elide.conventions)
}

elide {
publishing {
id = "graalvm-php"
name = "Elide PHP for GraalVM"
description = "Integration package with GraalVM, and PHP."

publish("jvm") {
from(components["kotlin"])
}
}

kotlin {
target = KotlinTarget.JVM
explicitApi = true
}

java {
// disable module-info processing (not present)
configureModularity = false
}

native {
target = NativeTarget.LIB
}
}

val oracleGvm = true

dependencies {
annotationProcessor(libs.graalvm.truffle.processor)
api(libs.graalvm.truffle.api)
implementation(libs.kotlinx.coroutines.core)
implementation(projects.packages.engine)
implementation(libs.json) // For JSON encode/decode support

compileOnly(libs.graalvm.svm)
if (oracleGvm) {
compileOnly(libs.graalvm.truffle.enterprise)
}

// Testing
testImplementation(projects.packages.test)
testImplementation(projects.packages.graalvm)
testImplementation(project(":packages:graalvm", configuration = "testBase"))
}

tasks.jar.configure {
exclude("**/runtime.current.json")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.lang.php;

import com.oracle.truffle.api.TruffleFile;
import java.io.IOException;
import java.nio.charset.Charset;

/** Detects PHP files based on file extension and content. */
public final class PhpFileDetector implements TruffleFile.FileTypeDetector {
@Override
public String findMimeType(TruffleFile file) throws IOException {
String fileName = file.getName();
if (fileName != null && fileName.endsWith(PhpLanguage.EXTENSION)) {
return PhpLanguage.MIME_TYPE;
}
return null;
}

@Override
public Charset findEncoding(TruffleFile file) throws IOException {
// PHP files are UTF-8 by default
return Charset.forName("UTF-8");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.lang.php;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
import elide.lang.php.nodes.PhpRootNode;
import elide.lang.php.parser.PhpParser;
import elide.lang.php.runtime.PhpContext;

/**
* TrufflePHP Language Implementation
*
* <p>This is the main entry point for the PHP language implementation in Truffle. It handles
* language initialization, context creation, and program execution.
*/
@TruffleLanguage.Registration(
id = "php",
name = "PHP",
defaultMimeType = PhpLanguage.MIME_TYPE,
characterMimeTypes = PhpLanguage.MIME_TYPE,
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
fileTypeDetectors = PhpFileDetector.class)
public final class PhpLanguage extends TruffleLanguage<PhpContext> {

public static final String ID = "php";
public static final String MIME_TYPE = "application/x-php";
public static final String EXTENSION = ".php";

@Override
protected PhpContext createContext(Env env) {
return new PhpContext(this, env);
}

@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
// Get the context to access the global scope
PhpContext context = getCurrentContext(PhpLanguage.class);
PhpParser parser = new PhpParser(this, request.getSource(), context.getGlobalScope());
return parser.parse().getCallTarget();
}

/**
* Parse and execute a source file. This is used by include/require statements to execute included
* files. The included file shares the same global scope and frame as the parent file.
*/
public Object parseAndExecute(Source source, VirtualFrame frame) {
PhpRootNode rootNode = parseSourceFile(source);
// Execute the included file's body directly in the current frame
// This allows the included file to access and modify variables from the parent scope
return rootNode.execute(frame);
}

@com.oracle.truffle.api.CompilerDirectives.TruffleBoundary
private PhpRootNode parseSourceFile(Source source) {
PhpContext context = getCurrentContext(PhpLanguage.class);
PhpParser parser = new PhpParser(this, source, context.getGlobalScope());
return parser.parse();
}

/** Get the language instance from a node. */
public static PhpLanguage get(Node node) {
return getCurrentLanguage(PhpLanguage.class);
}

/** Get the context associated with a node. */
public static PhpContext getContext(Node node) {
return getCurrentContext(PhpLanguage.class);
}
}
Loading
Loading