1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-10-20 03:24:04 +00:00

Migration from Nashorn to GraalJS

This commit is contained in:
Karol Stasiak 2020-09-22 17:59:32 +02:00
parent 1decf2f087
commit b87c40fc9c
3 changed files with 78 additions and 54 deletions

View File

@ -50,7 +50,10 @@ Test suite is useful if you plan on modifying the compiler. Some test dependenci
#### Prerequisites #### Prerequisites
* JDK 1.8 with Nashorn (tests don't work on newer versions) * JDK 1.8 or later
* Millfork up to version 0.3.22 used to require exactly JDK 1.8 with Nashorn,
as the tests didn't work on newer versions
* sbt * sbt

View File

@ -32,6 +32,12 @@ libraryDependencies += "eu.rekawek.coffeegb" % "coffee-gb" % "1.0.0" % "test"
libraryDependencies += "roug.org.osnine" % "osnine-core" % "2.0-SNAPSHOT" % "test" libraryDependencies += "roug.org.osnine" % "osnine-core" % "2.0-SNAPSHOT" % "test"
libraryDependencies += "org.graalvm.sdk" % "graal-sdk" % "20.2.0" % "test"
libraryDependencies += "org.graalvm.js" % "js" % "20.2.0" % "test"
libraryDependencies += "org.graalvm.js" % "js-scriptengine" % "20.2.0" % "test"
mainClass in Compile := Some("millfork.Main") mainClass in Compile := Some("millfork.Main")
assemblyJarName := "millfork.jar" assemblyJarName := "millfork.jar"

View File

@ -1,27 +1,27 @@
package millfork.test.emu package millfork.test.emu
import javax.script.{Bindings, ScriptEngine, ScriptEngineManager} import java.nio.file.{Files, Paths}
import java.io.FileReader
import java.nio.file.Paths
import jdk.nashorn.api.scripting.ScriptObjectMirror import org.graalvm.polyglot.{Context, Value}
import scala.language.dynamics import scala.language.dynamics
/** /**
* Despite its name, it no longer uses Nashorn, but GraalJS instead.
* @author Karol Stasiak * @author Karol Stasiak
*/ */
object NashornEmulator { object NashornEmulator {
lazy val engine: ScriptEngine = { lazy val engine: Context = {
val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI).toFile val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI)
val engine: ScriptEngine = new ScriptEngineManager().getEngineByName("nashorn") import org.graalvm.polyglot.Context
engine.eval(new FileReader(jsFile)) val polyglot = Context.create()
engine polyglot.eval("js", new String(Files.readAllBytes(jsFile)))
polyglot
} }
private def newCpu(): JsObject = { private def newCpu(): JsObject = {
JsObject(engine.get("CPU_65816").asInstanceOf[ScriptObjectMirror]).construct() JsObject(engine.eval("js", "CPU_65816")).construct()
} }
def run(memory: Array[Byte], steps: Long, start: Int): (Long, Array[Byte]) = { def run(memory: Array[Byte], steps: Long, start: Int): (Long, Array[Byte]) = {
@ -32,14 +32,15 @@ object NashornEmulator {
val memory0 = cpu.mmu.memory.!("0") val memory0 = cpu.mmu.memory.!("0")
for (i <- 0 until 1 << 16) memory0.!(i.toString, memory(i) & 0xff) for (i <- 0 until 1 << 16) memory0.!(i.toString, memory(i) & 0xff)
cpu.r.pc = start cpu.r.pc = start
cpu.r.sp = 0x1ff
cpu.r.k = 0 cpu.r.k = 0
var count = 0L var count = 0L
while (count < steps && cpu.r.s.*[Number].intValue().&(0xff) > 1) { while (count < steps && cpu.r.s.toInt.&(0xff) > 1) {
cpu.step() cpu.step()
count += 1 count += 1
} }
val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).*[Number].byteValue()).toArray val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).toByte).toArray
val cycles = cpu.cycle_count.*[Number].longValue() val cycles = cpu.cycle_count.toLong
cycles -> newMemory cycles -> newMemory
} }
@ -52,60 +53,74 @@ object NashornEmulator {
} }
} }
case class JsObject(private val mirror: Any) extends Dynamic { case class JsObject(private val mirror: Value) extends Dynamic {
def !(index: Long): JsObject = {
if (mirror.hasArrayElements) return JsObject(mirror.getArrayElement(index))
else throw new IllegalArgumentException(s"Accessing index $index of $getUnderlyingClass")
}
def !(index: JsObject): JsObject = def !(index: String): JsObject = {
mirror match { if (mirror.hasMembers) return JsObject(mirror.getMember(index))
case x: ScriptObjectMirror => JsObject(x.get(index.mirror)) else throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass")
case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") }
}
def !(index: Any): JsObject = def !(index: String, value: JsObject): Unit = {
mirror match { if (mirror.hasMembers) mirror.putMember(index, value.mirror)
case x: ScriptObjectMirror => JsObject(x.get(index)) else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass")
case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") }
}
def !(index: String, value:Any): Unit = def !(index: String, value:Any): Unit = {
mirror match { if (mirror.hasMembers) mirror.putMember(index, value)
case x: ScriptObjectMirror => x.setMember(index, value) else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass")
case _ => throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") }
}
def selectDynamic(name: String): JsObject = def !(index: Long, value: JsObject): Unit = {
mirror match { if (mirror.hasArrayElements) mirror.setArrayElement(index, value.mirror)
case x: ScriptObjectMirror => JsObject(x.get(name).asInstanceOf[Any] match { else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass")
case y: Double => if (y.isValidInt) y.toInt else y }
case y => y
}) def !(index: Long, value:Any): Unit = {
case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") if (mirror.hasArrayElements) mirror.setArrayElement(index, value)
} else throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass")
}
def selectDynamic(name: String): JsObject = {
if (mirror.hasMembers) return JsObject(mirror.getMember(name))
else throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass")
}
private def getUnderlyingClass = { private def getUnderlyingClass = {
if (mirror.asInstanceOf[AnyRef] eq null) "null" else mirror.getClass.getSimpleName if (mirror.asInstanceOf[AnyRef] eq null) "null" else mirror.getClass.getSimpleName
} }
def updateDynamic(name: String)(value: Any): Unit = def updateDynamic(name: String)(value: Any): Unit = {
mirror match { if (mirror.hasMembers) {
case x: ScriptObjectMirror => x.setMember(name, value) value match {
case _ => throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass") case o:JsObject =>
mirror.putMember(name, o.mirror)
case _ =>
mirror.putMember(name, value)
}
} }
else throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass")
}
def applyDynamic(name: String)(params: Any*): JsObject = def applyDynamic(name: String)(params: Any*): JsObject = {
mirror match { if (mirror.canInvokeMember(name)) JsObject(mirror.invokeMember(name, params.toArray))
case x: ScriptObjectMirror => JsObject(x.callMember(name, params.toArray)) else throw new IllegalArgumentException(s"Invoking method $name of $getUnderlyingClass")
case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") }
}
def construct(params: Any*): JsObject = def construct(params: Any*): JsObject = {
mirror match { if (mirror.canInstantiate) JsObject(mirror.newInstance(params.toArray))
case x: ScriptObjectMirror => JsObject(x.newObject(params.toArray)) else throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor")
case _ => throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor") }
}
@inline def toInt: Int = mirror.asInt()
def *[T]: T = mirror.asInstanceOf[T]
def toLong: Long = mirror.asLong()
def toByte: Byte = mirror.asInt().toByte
override def toString: String = String.valueOf(mirror) override def toString: String = String.valueOf(mirror)
} }