mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-21 03:34:04 +09:00
printing stacktrace on host exception
This commit is contained in:
@@ -895,8 +895,12 @@ shell.execute = function(line, nameOverride) {
|
|||||||
catch (e) {
|
catch (e) {
|
||||||
gotError = true
|
gotError = true
|
||||||
|
|
||||||
serial.printerr(`[command.js] program quit with ${e}:\n${e.stack || '(stack trace unavailable)'}`)
|
// A host (Java) exception has no JS `.stack`, so `e.stack` alone is
|
||||||
printerrln(`Program quit with ${e}:\n${e.stack || '(stack trace unavailable)'}`)
|
// "(stack trace unavailable)". Recover the real host trace (to stderr + string).
|
||||||
|
let hostTrace = ""
|
||||||
|
try { hostTrace = sys.printStackTrace(e) } catch (_) {}
|
||||||
|
serial.printerr(`[command.js] program quit with ${e}:\n${e.stack || hostTrace || '(stack trace unavailable)'}`)
|
||||||
|
printerrln(`Program quit with ${e}:\n${e.stack || hostTrace || '(stack trace unavailable)'}`)
|
||||||
|
|
||||||
if (`${e}`.startsWith("InterruptedException"))
|
if (`${e}`.startsWith("InterruptedException"))
|
||||||
errorlevel = SIGTERM.name
|
errorlevel = SIGTERM.name
|
||||||
|
|||||||
@@ -585,7 +585,7 @@ class AudioJSR223Delegate(private val vm: VM) {
|
|||||||
getFirstSnd()?.let { snd ->
|
getFirstSnd()?.let { snd ->
|
||||||
val ba = ByteArray(2304)
|
val ba = ByteArray(2304)
|
||||||
UnsafeHelper.memcpyRaw(null, snd.mediaDecodedBin.ptr, ba, UnsafeHelper.getArrayOffset(ba), 2304)
|
UnsafeHelper.memcpyRaw(null, snd.mediaDecodedBin.ptr, ba, UnsafeHelper.getArrayOffset(ba), 2304)
|
||||||
snd.playheads[playhead].pcmQueue.addLast(ba)
|
snd.playheads[playhead].pcmQueue.add(ba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,7 +600,7 @@ class AudioJSR223Delegate(private val vm: VM) {
|
|||||||
getFirstSnd()?.let { snd ->
|
getFirstSnd()?.let { snd ->
|
||||||
val ba = ByteArray(sampleLength * 2) // 32768 samples * 2 channels
|
val ba = ByteArray(sampleLength * 2) // 32768 samples * 2 channels
|
||||||
UnsafeHelper.memcpyRaw(null, snd.tadDecodedBin.ptr, ba, UnsafeHelper.getArrayOffset(ba), sampleLength * 2L)
|
UnsafeHelper.memcpyRaw(null, snd.tadDecodedBin.ptr, ba, UnsafeHelper.getArrayOffset(ba), sampleLength * 2L)
|
||||||
snd.playheads[playhead].pcmQueue.addLast(ba)
|
snd.playheads[playhead].pcmQueue.add(ba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -778,7 +778,7 @@ class VM(
|
|||||||
// MMIO area
|
// MMIO area
|
||||||
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
||||||
val fromIndex = (-from-1) / 131072
|
val fromIndex = (-from-1) / 131072
|
||||||
val dev = peripheralTable[fromIndex.toInt()].peripheral ?: return null
|
val dev = peripheralTable.getOrNull(fromIndex.toInt())?.peripheral ?: return null
|
||||||
val fromRel = (-from-1) % 131072
|
val fromRel = (-from-1) % 131072
|
||||||
if (fromRel + len > 131072) return null
|
if (fromRel + len > 131072) return null
|
||||||
|
|
||||||
@@ -807,7 +807,7 @@ class VM(
|
|||||||
// memory area
|
// memory area
|
||||||
else {
|
else {
|
||||||
val fromIndex = (-from-1) / 1048576
|
val fromIndex = (-from-1) / 1048576
|
||||||
val dev = peripheralTable[fromIndex.toInt()].peripheral ?: return null
|
val dev = peripheralTable.getOrNull(fromIndex.toInt())?.peripheral ?: return null
|
||||||
val fromRel = (-from-1) % 1048576
|
val fromRel = (-from-1) % 1048576
|
||||||
if (fromRel + len > 1048576) return null
|
if (fromRel + len > 1048576) return null
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class VMJSR223Delegate(private val vm: VM) {
|
|||||||
// MMIO area
|
// MMIO area
|
||||||
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
|
||||||
val fromIndex = ((-from-1) / 131072).absoluteValue
|
val fromIndex = ((-from-1) / 131072).absoluteValue
|
||||||
val dev = vm.peripheralTable[fromIndex.toInt()].peripheral ?: return null
|
val dev = vm.peripheralTable.getOrNull(fromIndex.toInt())?.peripheral ?: return null
|
||||||
val fromRel = (-from-1) % 131072
|
val fromRel = (-from-1) % 131072
|
||||||
if (fromRel + len > 131072) return null
|
if (fromRel + len > 131072) return null
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class VMJSR223Delegate(private val vm: VM) {
|
|||||||
// memory area
|
// memory area
|
||||||
else {
|
else {
|
||||||
val fromIndex = (-from-1) / 1048576
|
val fromIndex = (-from-1) / 1048576
|
||||||
val dev = vm.peripheralTable[fromIndex.toInt()].peripheral ?: return null
|
val dev = vm.peripheralTable.getOrNull(fromIndex.toInt())?.peripheral ?: return null
|
||||||
val fromRel = (-from-1) % 1048576
|
val fromRel = (-from-1) % 1048576
|
||||||
if (fromRel + len > 1048576) return null
|
if (fromRel + len > 1048576) return null
|
||||||
|
|
||||||
@@ -88,6 +88,56 @@ class VMJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
fun getVmId() = vm.id.toString()
|
fun getVmId() = vm.id.toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recover the FULL host (Java) stack trace of an exception caught in JS, write it to the host
|
||||||
|
* stderr, and also RETURN it as a string so the caller can show it on the VM screen too.
|
||||||
|
*
|
||||||
|
* When a host (Kotlin) function throws, GraalVM surfaces it to JS as an exception object whose
|
||||||
|
* `toString()` is only the message — so `catch (e) { printerrln(e) }` shows the message on the
|
||||||
|
* VM screen while the Java stack trace (the part that says *where* it blew up) is lost and the
|
||||||
|
* host stdout/stderr gets nothing. Re-throwing the caught value back across the polyglot
|
||||||
|
* boundary recovers the original Throwable so its stack trace can be printed. Call from a JS
|
||||||
|
* catch block: `let tr = sys.printStackTrace(e)` (then optionally `printerrln(tr)`).
|
||||||
|
*/
|
||||||
|
fun printStackTrace(e: org.graalvm.polyglot.Value?): String {
|
||||||
|
val sb = StringBuilder("===== host stack trace =====\n")
|
||||||
|
if (e == null) {
|
||||||
|
sb.append("(the caught value was null/undefined)\n")
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (e.isException) {
|
||||||
|
e.throwException() // throws a PolyglotException wrapping the original Throwable
|
||||||
|
} else if (e.isHostObject && e.asHostObject<Any?>() is Throwable) {
|
||||||
|
// Some configs surface a caught host exception as a host object, not an
|
||||||
|
// exception object — unwrap the Throwable directly.
|
||||||
|
val sw = java.io.StringWriter()
|
||||||
|
(e.asHostObject<Any?>() as Throwable).printStackTrace(java.io.PrintWriter(sw))
|
||||||
|
sb.append("host object Throwable:\n").append(sw)
|
||||||
|
} else {
|
||||||
|
sb.append("caught value is not an exception object: ").append(e).append('\n')
|
||||||
|
}
|
||||||
|
} catch (pe: org.graalvm.polyglot.PolyglotException) {
|
||||||
|
if (pe.isHostException) {
|
||||||
|
val sw = java.io.StringWriter()
|
||||||
|
pe.asHostException().printStackTrace(java.io.PrintWriter(sw))
|
||||||
|
sb.append("host (Java) exception:\n").append(sw)
|
||||||
|
} else {
|
||||||
|
sb.append("guest exception: ").append(pe.message).append('\n')
|
||||||
|
}
|
||||||
|
sb.append("guest stack frames:\n")
|
||||||
|
for (frame in pe.polyglotStackTrace) sb.append(" at ").append(frame).append('\n')
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
val sw = java.io.StringWriter()
|
||||||
|
t.printStackTrace(java.io.PrintWriter(sw))
|
||||||
|
sb.append("could not recover the trace; got:\n").append(sw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val s = sb.toString()
|
||||||
|
System.err.println(s)
|
||||||
|
System.err.flush()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte())
|
fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte())
|
||||||
fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255)
|
fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package net.torvald.tsvm.peripheral
|
|||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
||||||
import com.badlogic.gdx.utils.GdxRuntimeException
|
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||||
import com.badlogic.gdx.utils.Queue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import io.airlift.compress.zstd.ZstdInputStream
|
import io.airlift.compress.zstd.ZstdInputStream
|
||||||
import net.torvald.UnsafeHelper
|
import net.torvald.UnsafeHelper
|
||||||
import net.torvald.UnsafePtr
|
import net.torvald.UnsafePtr
|
||||||
@@ -32,21 +32,26 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
|||||||
|
|
||||||
val writeQueue = playhead.pcmQueue
|
val writeQueue = playhead.pcmQueue
|
||||||
|
|
||||||
if (playhead.isPlaying && writeQueue.notEmpty()) {
|
if (playhead.isPlaying) {
|
||||||
|
|
||||||
printdbg("Taking samples from queue (queue size: ${writeQueue.size}/${playhead.getPcmQueueCapacity()})")
|
// Single atomic poll — `pcmQueue` has concurrent producers (queueing thread /
|
||||||
|
// JS thread), so a separate notEmpty()/removeFirst() would race and corrupt it.
|
||||||
|
val samples = writeQueue.poll()
|
||||||
|
|
||||||
val samples = writeQueue.removeFirst()
|
if (samples != null) {
|
||||||
playhead.position = writeQueue.size
|
printdbg("Taking samples from queue (queue size: ${writeQueue.size}/${playhead.getPcmQueueCapacity()})")
|
||||||
|
|
||||||
playhead.audioDevice.writeSamplesUI8(samples, 0, samples.size)
|
playhead.position = writeQueue.size
|
||||||
|
|
||||||
Thread.sleep(6)
|
playhead.audioDevice.writeSamplesUI8(samples, 0, samples.size)
|
||||||
}
|
|
||||||
else if (playhead.isPlaying && writeQueue.isEmpty) {
|
|
||||||
printdbg("!! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED ")
|
|
||||||
|
|
||||||
Thread.sleep(6)
|
Thread.sleep(6)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printdbg("!! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED ")
|
||||||
|
|
||||||
|
Thread.sleep(6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Tracker mode
|
// Tracker mode
|
||||||
@@ -92,7 +97,7 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm
|
|||||||
UnsafeHelper.getArrayOffset(samples),
|
UnsafeHelper.getArrayOffset(samples),
|
||||||
it.pcmUploadLength.toLong()
|
it.pcmUploadLength.toLong()
|
||||||
)
|
)
|
||||||
it.pcmQueue.addLast(samples)
|
it.pcmQueue.add(samples)
|
||||||
|
|
||||||
it.pcmUploadLength = 0
|
it.pcmUploadLength = 0
|
||||||
it.position = it.pcmQueue.size
|
it.position = it.pcmQueue.size
|
||||||
@@ -442,11 +447,11 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
|
|
||||||
val writeQueue = playhead.pcmQueue
|
val writeQueue = playhead.pcmQueue
|
||||||
|
|
||||||
if (playhead.isPlaying && writeQueue.notEmpty()) {
|
val samples = if (playhead.isPlaying) writeQueue.poll() else null
|
||||||
|
if (samples != null) {
|
||||||
|
|
||||||
printdbg("Taking samples from queue (queue size: ${writeQueue.size})")
|
printdbg("Taking samples from queue (queue size: ${writeQueue.size})")
|
||||||
|
|
||||||
val samples = writeQueue.removeFirst()
|
|
||||||
playhead.position = writeQueue.size
|
playhead.position = writeQueue.size
|
||||||
|
|
||||||
// printdbg("P${playhead.index+1} Vol ${playhead.masterVolume}; LpP ${playhead.pcmUploadLength}; start playback...")
|
// printdbg("P${playhead.index+1} Vol ${playhead.masterVolume}; LpP ${playhead.pcmUploadLength}; start playback...")
|
||||||
@@ -4793,7 +4798,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
var globalVolume: Int = 0x80, // 8-bit, default $80 (spec §5). Mutated by V $xx00.
|
var globalVolume: Int = 0x80, // 8-bit, default $80 (spec §5). Mutated by V $xx00.
|
||||||
var mixingVolume: Int = 0x80, // 8-bit, default $80 (spec §5). Final-mix scaler, set once per song.
|
var mixingVolume: Int = 0x80, // 8-bit, default $80 (spec §5). Final-mix scaler, set once per song.
|
||||||
|
|
||||||
var pcmQueue: Queue<ByteArray> = Queue<ByteArray>(),
|
// PCM playback queue. ConcurrentLinkedQueue (not the libGDX Queue) because it is written
|
||||||
|
// by the queueing thread (WAV/raw PCM via WriteQueueingRunnable) and the JS thread
|
||||||
|
// (mp2UploadDecoded / tadUploadDecoded), drained by the render thread, and purged by the JS
|
||||||
|
// thread — concurrent addLast/removeFirst on a non-thread-safe queue corrupted head/tail and
|
||||||
|
// threw a sporadic ArrayIndexOutOfBoundsException during PCM playback.
|
||||||
|
var pcmQueue: ConcurrentLinkedQueue<ByteArray> = ConcurrentLinkedQueue<ByteArray>(),
|
||||||
var pcmQueueSizeIndex: Int = 0,
|
var pcmQueueSizeIndex: Int = 0,
|
||||||
val audioDevice: OpenALBufferedAudioDevice,
|
val audioDevice: OpenALBufferedAudioDevice,
|
||||||
) {
|
) {
|
||||||
@@ -4825,7 +4835,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
if (field && !value) {
|
if (field && !value) {
|
||||||
// println("!! inserting dummy bytes")
|
// println("!! inserting dummy bytes")
|
||||||
if (isPcmMode) {
|
if (isPcmMode) {
|
||||||
pcmQueue.addLast(ByteArray(audioDevice.bufferSize * audioDevice.bufferCount))
|
pcmQueue.add(ByteArray(audioDevice.bufferSize * audioDevice.bufferCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
field = value
|
field = value
|
||||||
|
|||||||
Reference in New Issue
Block a user