Fixed bugs in ASM editor signature help. The bytecode IR now contains all ASM instruction arguments, even when they are the wrong type or there are too many.

This commit is contained in:
Daan Vanden Bosch 2021-04-25 16:53:26 +02:00
parent b973c99c6a
commit 797c5a298e
19 changed files with 549 additions and 467 deletions

View File

@ -30,9 +30,3 @@ fun Int.setBit(bit: Int, value: Boolean): Int =
} else {
this and (1 shl bit).inv()
}
expect fun Int.reinterpretAsFloat(): Float
expect fun Float.reinterpretAsInt(): Int
expect fun Float.reinterpretAsUInt(): UInt

View File

@ -1,10 +1,5 @@
package world.phantasmal.core
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.DataView
private val dataView = DataView(ArrayBuffer(4))
@Suppress("NOTHING_TO_INLINE")
actual inline fun Char.fastIsWhitespace(): Boolean =
asDynamic() == 0x20 || (asDynamic() >= 0x09 && asDynamic() <= 0x0D)
@ -12,18 +7,3 @@ actual inline fun Char.fastIsWhitespace(): Boolean =
@Suppress("NOTHING_TO_INLINE")
actual inline fun Char.isDigit(): Boolean =
asDynamic() >= 0x30 && asDynamic() <= 0x39
actual fun Int.reinterpretAsFloat(): Float {
dataView.setInt32(0, this)
return dataView.getFloat32(0)
}
actual fun Float.reinterpretAsInt(): Int {
dataView.setFloat32(0, this)
return dataView.getInt32(0)
}
actual fun Float.reinterpretAsUInt(): UInt {
dataView.setFloat32(0, this)
return dataView.getUint32(0).toUInt()
}

View File

@ -2,17 +2,8 @@
package world.phantasmal.core
import java.lang.Float.floatToIntBits
import java.lang.Float.intBitsToFloat
@Suppress("NOTHING_TO_INLINE")
actual inline fun Char.fastIsWhitespace(): Boolean = isWhitespace()
@Suppress("NOTHING_TO_INLINE")
actual inline fun Char.isDigit(): Boolean = this in '0'..'9'
actual fun Int.reinterpretAsFloat(): Float = intBitsToFloat(this)
actual fun Float.reinterpretAsInt(): Int = floatToIntBits(this)
actual fun Float.reinterpretAsUInt(): UInt = reinterpretAsInt().toUInt()

View File

@ -131,8 +131,9 @@ private class Assembler(private val asm: List<String>, private val inlineStackAr
opcode: Opcode,
args: List<Arg>,
mnemonicSrcLoc: SrcLoc?,
valid: Boolean,
argSrcLocs: List<ArgSrcLoc>,
stackArgSrcLocs: List<ArgSrcLoc>,
trailingArgSeparator: Boolean,
) {
when (val seg = segment) {
null -> {
@ -151,11 +152,12 @@ private class Assembler(private val asm: List<String>, private val inlineStackAr
Instruction(
opcode,
args,
valid,
InstructionSrcLoc(
mnemonic = mnemonicSrcLoc,
args = argSrcLocs,
stackArgs = stackArgSrcLocs,
)
trailingArgSeparator,
),
)
)
}
@ -359,200 +361,169 @@ private class Assembler(private val asm: List<String>, private val inlineStackAr
if (opcode == null) {
addError("Unknown opcode.")
} else {
// Inline arguments.
val inlineArgs = mutableListOf<Arg>()
val inlineArgSrcLocs = mutableListOf<ArgSrcLoc>()
// Stack arguments.
val stackArgs = mutableListOf<Arg>()
val stackArgSrcLocs = mutableListOf<ArgSrcLoc>()
if (opcode.stack !== StackInteraction.Pop) {
// Arguments should be inlined right after the opcode.
if (!parseArgs(
opcode,
mnemonicSrcLoc.col,
inlineArgs,
inlineArgSrcLocs,
stack = false,
)
) {
return
}
// Arguments should be inlined immediately after the opcode.
parseArgs(
opcode,
mnemonicSrcLoc,
stack = false,
)
} else {
// Arguments should be passed to the opcode via the stack.
if (!parseArgs(
opcode,
mnemonicSrcLoc.col,
stackArgs,
stackArgSrcLocs,
stack = true,
)
) {
return
}
parseArgs(
opcode,
mnemonicSrcLoc,
stack = true,
)
}
addInstruction(
opcode,
inlineArgs,
mnemonicSrcLoc,
inlineArgSrcLocs,
stackArgSrcLocs,
)
}
}
/**
* Returns true iff arguments can be translated to byte code, possibly after truncation.
*/
private fun parseArgs(
opcode: Opcode,
startCol: Int,
args: MutableList<Arg>,
srcLocs: MutableList<ArgSrcLoc>,
stack: Boolean,
): Boolean {
private fun parseArgs(opcode: Opcode, mnemonicSrcLoc: SrcLoc, stack: Boolean) {
val immediateArgs = mutableListOf<Arg>()
val srcLocs = mutableListOf<ArgSrcLoc>()
var argCount = 0
var semiValid = true
var valid = true
var shouldBeArg = true
var paramI = 0
var prevToken: Token?
var prevCol: Int
var prevLen: Int
var token = tokenizer.type
var col = tokenizer.col
var len = tokenizer.len
tokenizer.nextToken()
while (true) {
// Previous token data.
prevToken = token
prevCol = col
prevLen = len
val token = tokenizer.type
val value = tokenizer.value
// Current token data.
token = tokenizer.type
col = tokenizer.col
len = tokenizer.len
val value = tokenizer.value
if (token == null) {
break
}
// Next token data.
tokenizer.nextToken()
val nextToken = tokenizer.type
val nextCol = tokenizer.col
val nextLen = tokenizer.len
val param = opcode.params.getOrNull(paramI)
val paramType = param?.type
// Coarse source position, including surrounding whitespace.
val coarseCol = prevCol + prevLen
val coarseLen =
if (tokenizer.type === Token.ArgSeparator) tokenizer.col + tokenizer.len - coarseCol
else tokenizer.col - coarseCol
if (nextToken === Token.ArgSeparator) nextCol + nextLen - coarseCol
else nextCol - coarseCol
if (token === Token.ArgSeparator) {
if (shouldBeArg) {
addError("Expected an argument.")
} else if (param == null || !param.varargs) {
paramI++
}
shouldBeArg = true
} else {
if (!shouldBeArg) {
addError(coarseCol, col - coarseCol, "Expected a comma.")
}
shouldBeArg = false
if (token !== Token.ArgSeparator) {
argCount++
}
if (paramI < opcode.params.size) {
val param = opcode.params[paramI]
// Try to match token type to parameter type.
var typeMatch: Boolean
if (token === Token.ArgSeparator) {
if (shouldBeArg) {
addError("Expected an argument.")
} else if (!param.varargs) {
paramI++
}
// If arg is nonnull, types match and argument is syntactically valid.
val arg: Arg = when (token) {
Token.Int32 -> {
value as Int
shouldBeArg = true
} else {
if (!shouldBeArg) {
addError(coarseCol, col - coarseCol, "Expected a comma.")
}
shouldBeArg = false
// Try to match token type parameter type.
var typeMatch: Boolean
// If arg is nonnull, types match and argument is syntactically valid.
val arg: Arg? = when (token) {
Token.Int32 -> {
value as Int
when (param.type) {
ByteType -> {
typeMatch = true
intValueToArg(value, 1)
}
ShortType,
is LabelType,
-> {
typeMatch = true
intValueToArg(value, 2)
}
IntType -> {
typeMatch = true
intValueToArg(value, 4)
}
FloatType -> {
typeMatch = true
Arg(value.toFloat())
}
else -> {
typeMatch = false
null
}
when (paramType) {
ByteType -> {
typeMatch = true
checkIntValue(col, len, value, 1)
}
}
Token.Float32 -> {
typeMatch = param.type === FloatType
if (typeMatch) {
Arg(value as Float)
} else {
null
ShortType,
is LabelType,
-> {
typeMatch = true
checkIntValue(col, len, value, 2)
}
}
Token.Register -> {
value as Int
typeMatch = stack ||
param.type === RegVarType ||
param.type is RegType
if (value > 255) {
addError("Invalid register reference, expected r0-r255.")
null
} else {
Arg(value)
IntType -> {
typeMatch = true
checkIntValue(col, len, value, 4)
}
}
Token.Str -> {
typeMatch = param.type === StringType
if (typeMatch) {
Arg(value as String)
} else {
null
FloatType -> {
typeMatch = true
FloatArg(value.toFloat())
}
else -> {
typeMatch = false
IntArg(value)
}
}
else -> {
typeMatch = false
null
}
}
val srcLoc = ArgSrcLoc(
precise = SrcLoc(lineNo, col, len),
coarse = SrcLoc(lineNo, coarseCol, coarseLen),
)
if (arg != null) {
args.add(arg)
srcLocs.add(srcLoc)
Token.Float32 -> {
typeMatch = paramType === FloatType
FloatArg(value as Float)
}
if (!typeMatch) {
semiValid = false
Token.Register -> {
typeMatch = stack ||
paramType === RegVarType ||
paramType is RegType
val typeStr: String? = when (param.type) {
value as Int
if (value > 255) {
addError(col, len, "Invalid register reference, expected r0-r255.")
}
IntArg(value)
}
Token.Str -> {
typeMatch = paramType === StringType
StringArg(value as String)
}
else -> {
typeMatch = false
UnknownArg(value!!)
}
}
val srcLoc = ArgSrcLoc(
precise = SrcLoc(lineNo, col, len),
coarse = SrcLoc(lineNo, coarseCol, coarseLen),
)
if (!stack) {
immediateArgs.add(arg)
}
srcLocs.add(srcLoc)
if (!typeMatch) {
valid = false
// Don't add a type errors for surplus arguments.
if (param != null) {
val typeStr = when (param.type) {
ByteType -> "an 8-bit integer"
ShortType -> "a 16-bit integer"
IntType -> "a 32-bit integer"
@ -573,93 +544,103 @@ private class Assembler(private val asm: List<String>, private val inlineStackAr
is RegType,
-> "a register reference"
else -> null
PointerType -> "a pointer" // No known opcodes directly take a pointer.
AnyType.Instance -> "an argument" // Should never happen.
}
addError(
if (typeStr == null) "Unexpected token." else "Expected ${typeStr}."
)
} else if (stack && arg != null) {
// Inject stack push instructions if necessary.
// If the token is a register, push it as a register, otherwise coerce type.
if (token === Token.Register) {
if (param.type is RegType) {
addError(col, len, "Expected ${typeStr}.")
}
} else if (stack) {
// Inject stack push instructions if necessary.
checkNotNull(paramType)
// If the token is a register, push it as a register, otherwise coerce type.
if (token === Token.Register) {
if (paramType is RegType) {
addInstruction(
OP_ARG_PUSHB,
listOf(arg),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
} else {
addInstruction(
OP_ARG_PUSHR,
listOf(arg),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
}
} else {
when (paramType) {
ByteType,
is RegType,
-> {
addInstruction(
OP_ARG_PUSHB,
listOf(arg),
null,
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
emptyList(),
)
} else {
addInstruction(
OP_ARG_PUSHR,
listOf(arg),
null,
listOf(srcLoc),
emptyList(),
trailingArgSeparator = false,
)
}
} else {
when (param.type) {
ByteType,
is RegType,
-> {
addInstruction(
OP_ARG_PUSHB,
listOf(arg),
null,
listOf(srcLoc),
emptyList(),
)
}
ShortType,
is LabelType,
-> {
addInstruction(
OP_ARG_PUSHW,
listOf(arg),
null,
listOf(srcLoc),
emptyList(),
)
}
ShortType,
is LabelType,
-> {
addInstruction(
OP_ARG_PUSHW,
listOf(arg),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
}
IntType -> {
addInstruction(
OP_ARG_PUSHL,
listOf(arg),
null,
listOf(srcLoc),
emptyList(),
)
}
IntType -> {
addInstruction(
OP_ARG_PUSHL,
listOf(arg),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
}
FloatType -> {
addInstruction(
OP_ARG_PUSHL,
listOf(Arg((arg.value as Float).toRawBits())),
null,
listOf(srcLoc),
emptyList(),
)
}
// Floats are pushed as ints.
FloatType -> {
addInstruction(
OP_ARG_PUSHL,
listOf(IntArg((arg as FloatArg).value.toRawBits())),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
}
StringType -> {
addInstruction(
OP_ARG_PUSHS,
listOf(arg),
null,
listOf(srcLoc),
emptyList(),
)
}
StringType -> {
addInstruction(
OP_ARG_PUSHS,
listOf(arg),
mnemonicSrcLoc = null,
valid = true,
listOf(srcLoc),
trailingArgSeparator = false,
)
}
else -> {
logger.error {
"Line $lineNo: Type ${param.type::class} not implemented."
}
else -> {
logger.error {
"Line $lineNo: Type ${paramType::class} not implemented."
}
}
}
@ -672,59 +653,67 @@ private class Assembler(private val asm: List<String>, private val inlineStackAr
if (!inlineStackArgs && opcode.stack === StackInteraction.Pop) 0
else opcode.params.size
val errorLength = prevCol + prevLen - startCol
val trailingArgSeparator = prevToken === Token.ArgSeparator
if (!opcode.varargs && argCount != paramCount) {
semiValid = argCount >= paramCount
addError(
startCol,
errorLength,
"Expected $paramCount argument${
if (paramCount == 1) "" else "s"
}, got $argCount.",
)
} else if (opcode.varargs && argCount < paramCount) {
semiValid = argCount >= paramCount - 1
// TODO: This check assumes we want at least 1 argument for a vararg parameter.
// Is this correct?
addError(
startCol,
errorLength,
"Expected at least $paramCount argument${
if (paramCount == 1) "" else "s"
}, got $argCount.",
)
// Length from the start of the mnemonic until the end of the last token.
val errorLength = prevCol + prevLen - mnemonicSrcLoc.col
if (opcode.varargs) {
// Argument count should be equal to or greater than the amount of parameters for variadic
// opcodes.
if (argCount < paramCount) {
valid = false
addError(
mnemonicSrcLoc.col,
errorLength,
"Expected at least $paramCount argument${
if (paramCount == 1) "" else "s"
}, got $argCount.",
)
}
} else {
// Argument count should match parameter count exactly for non-variadic opcodes.
if (argCount != paramCount) {
valid = false
addError(
mnemonicSrcLoc.col,
errorLength,
"Expected $paramCount argument${
if (paramCount == 1) "" else "s"
}, got $argCount.",
)
}
}
return semiValid
// Trailing argument separators are not allowed.
if (trailingArgSeparator) {
addError(prevCol, prevLen, "Unexpected comma.")
}
addInstruction(opcode, immediateArgs, mnemonicSrcLoc, valid, srcLocs, trailingArgSeparator)
}
private fun intValueToArg(value: Int, size: Int): Arg? {
private fun checkIntValue(col: Int, len: Int, value: Int, size: Int): Arg {
// Fast-path 32-bit ints for improved JS perf. Otherwise maxValue would have to be a Long
// or UInt, which incurs a perf hit in JS.
if (size == 4) {
return Arg(value)
} else {
if (size != 4) {
val bitSize = 8 * size
// Minimum of the signed version of this integer type.
val minValue = -(1 shl (bitSize - 1))
// Maximum of the unsigned version of this integer type.
val maxValue = (1 shl (bitSize)) - 1
return when {
when {
value < minValue -> {
addError("${bitSize}-Bit integer can't be less than ${minValue}.")
null
addError(col, len, "${bitSize}-Bit integer can't be less than ${minValue}.")
}
value > maxValue -> {
addError("${bitSize}-Bit integer can't be greater than ${maxValue}.")
null
}
else -> {
Arg(value)
addError(col, len, "${bitSize}-Bit integer can't be greater than ${maxValue}.")
}
}
}
return IntArg(value)
}
private fun parseBytes() {

View File

@ -103,6 +103,7 @@ class Instruction(
* Immediate arguments for the opcode.
*/
val args: List<Arg>,
val valid: Boolean,
val srcLoc: InstructionSrcLoc?,
) {
/**
@ -144,41 +145,19 @@ class Instruction(
}
/**
* Returns the source locations of the immediate arguments for the parameter at the given index.
* Returns the source locations of the (immediate or stack) arguments for the parameter at the
* given index.
*/
fun getArgSrcLocs(paramIndex: Int): List<ArgSrcLoc> {
val argSrcLocs = srcLoc?.args
?: return emptyList()
val param = opcode.params[paramIndex]
// Variable length arguments are always last, so we can just gobble up all SrcLocs from
// paramIndex onward.
return if (param.varargs) {
return if (opcode.params[paramIndex].varargs) {
// Variadic parameters are always last, so we can just gobble up all SrcLocs from
// paramIndex onward.
argSrcLocs.drop(paramIndex)
} else {
listOf(argSrcLocs[paramIndex])
}
}
/**
* Returns the source locations of the stack arguments for the parameter at the given index.
*/
fun getStackArgSrcLocs(paramIndex: Int): List<ArgSrcLoc> {
val argSrcLocs = srcLoc?.stackArgs
if (argSrcLocs == null || paramIndex > argSrcLocs.lastIndex) {
return emptyList()
}
val param = opcode.params[paramIndex]
// Variable length arguments are always last, so we can just gobble up all SrcLocs from
// paramIndex onward.
return if (param.varargs) {
argSrcLocs.drop(paramIndex)
} else {
listOf(argSrcLocs[paramIndex])
listOfNotNull(argSrcLocs.getOrNull(paramIndex))
}
}
@ -210,9 +189,9 @@ class Instruction(
StringType -> {
if (dcGcFormat) {
(args[0].value as String).length + 1
(args[0] as StringArg).value.length + 1
} else {
2 * (args[0].value as String).length + 2
2 * (args[0] as StringArg).value.length + 2
}
}
@ -232,13 +211,43 @@ class Instruction(
}
fun copy(): Instruction =
Instruction(opcode, args, srcLoc)
Instruction(opcode, args, valid, srcLoc).also { it.paramToArgs = paramToArgs }
}
/**
* Instruction argument.
*/
data class Arg(val value: Any)
sealed class Arg {
abstract val value: Any?
abstract fun coerceInt(): Int
abstract fun coerceFloat(): Float
abstract fun coerceString(): String
}
data class IntArg(override val value: Int) : Arg() {
override fun coerceInt(): Int = value
override fun coerceFloat(): Float = Float.fromBits(value)
override fun coerceString(): String = value.toString()
}
data class FloatArg(override val value: Float) : Arg() {
override fun coerceInt(): Int = value.toRawBits()
override fun coerceFloat(): Float = value
override fun coerceString(): String = value.toString()
}
data class StringArg(override val value: String) : Arg() {
override fun coerceInt(): Int = 0
override fun coerceFloat(): Float = 0f
override fun coerceString(): String = value
}
data class UnknownArg(override val value: Any?) : Arg() {
override fun coerceInt(): Int = 0
override fun coerceFloat(): Float = 0f
override fun coerceString(): String = ""
}
/**
* Position and length of related source assembly code.
@ -254,8 +263,15 @@ class SrcLoc(
*/
class InstructionSrcLoc(
val mnemonic: SrcLoc?,
/**
* Immediate or stack argument locations.
*/
val args: List<ArgSrcLoc> = emptyList(),
val stackArgs: List<ArgSrcLoc> = emptyList(),
/**
* Does the instruction end with a comma? This can be the case when a user has partially typed
* an instruction.
*/
val trailingArgSeparator: Boolean,
)
/**

View File

@ -1,7 +1,6 @@
package world.phantasmal.lib.asm
import mu.KotlinLogging
import world.phantasmal.core.reinterpretAsFloat
import kotlin.math.min
private val logger = KotlinLogging.logger {}
@ -212,7 +211,7 @@ private fun StringBuilder.appendArgs(params: List<Param>, args: List<ArgWithType
FloatType -> {
// Floats are pushed onto the stack as integers with arg_pushl.
if (stack) {
append((arg.value as Int).reinterpretAsFloat())
append(Float.fromBits((arg as IntArg).value))
} else {
append(arg.value)
}
@ -241,7 +240,7 @@ private fun StringBuilder.appendArgs(params: List<Param>, args: List<ArgWithType
}
StringType -> {
appendStringArg(arg.value as String)
appendStringArg((arg as StringArg).value)
}
else -> {

View File

@ -167,7 +167,7 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction
// Unconditional jump.
OP_JMP.code -> {
branchType = BranchType.Jump
branchLabels = listOf(inst.args[0].value as Int)
branchLabels = listOf((inst.args[0] as IntArg).value)
}
// Conditional jumps.
@ -175,7 +175,7 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction
OP_JMP_OFF.code,
-> {
branchType = BranchType.ConditionalJump
branchLabels = listOf(inst.args[0].value as Int)
branchLabels = listOf((inst.args[0] as IntArg).value)
}
OP_JMP_E.code,
OP_JMPI_E.code,
@ -199,11 +199,11 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction
OP_JMPI_LE.code,
-> {
branchType = BranchType.ConditionalJump
branchLabels = listOf(inst.args[2].value as Int)
branchLabels = listOf((inst.args[2] as IntArg).value)
}
OP_SWITCH_JMP.code -> {
branchType = BranchType.ConditionalJump
branchLabels = inst.args.drop(1).map { it.value as Int }
branchLabels = inst.args.drop(1).map { (it as IntArg).value }
}
// Calls.
@ -211,11 +211,11 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction
OP_VA_CALL.code,
-> {
branchType = BranchType.Call
branchLabels = listOf(inst.args[0].value as Int)
branchLabels = listOf((inst.args[0] as IntArg).value)
}
OP_SWITCH_CALL.code -> {
branchType = BranchType.Call
branchLabels = inst.args.drop(1).map { it.value as Int }
branchLabels = inst.args.drop(1).map { (it as IntArg).value }
}
// All other opcodes.

View File

@ -1,10 +1,7 @@
package world.phantasmal.lib.asm.dataFlowAnalysis
import mu.KotlinLogging
import world.phantasmal.lib.asm.InstructionSegment
import world.phantasmal.lib.asm.OP_BB_MAP_DESIGNATE
import world.phantasmal.lib.asm.OP_MAP_DESIGNATE
import world.phantasmal.lib.asm.OP_MAP_DESIGNATE_EX
import world.phantasmal.lib.asm.*
private val logger = KotlinLogging.logger {}
@ -24,7 +21,7 @@ fun getMapDesignations(
cfg = createCfg()
}
val areaId = getRegisterValue(cfg, inst, inst.args[0].value as Int)
val areaId = getRegisterValue(cfg, inst, (inst.args[0] as IntArg).value)
if (areaId.size > 1) {
logger.warn {
@ -34,7 +31,7 @@ fun getMapDesignations(
}
val variantIdRegister =
inst.args[0].value as Int + (if (inst.opcode == OP_MAP_DESIGNATE) 2 else 3)
(inst.args[0] as IntArg).value + (if (inst.opcode == OP_MAP_DESIGNATE) 2 else 3)
val variantId = getRegisterValue(cfg, inst, variantIdRegister)
if (variantId.size > 1) {
@ -48,7 +45,7 @@ fun getMapDesignations(
}
OP_BB_MAP_DESIGNATE.code -> {
mapDesignations[inst.args[0].value as Int] = inst.args[2].value as Int
mapDesignations[(inst.args[0] as IntArg).value] = (inst.args[2] as IntArg).value
}
}
}

View File

@ -58,7 +58,7 @@ private class RegisterValueFinder {
OP_LET.code -> {
if (args[0].value == register) {
return find(LinkedHashSet(path), block, i, args[1].value as Int)
return find(LinkedHashSet(path), block, i, (args[1] as IntArg).value)
}
}
@ -68,7 +68,7 @@ private class RegisterValueFinder {
OP_SYNC_LETI.code,
-> {
if (args[0].value == register) {
return ValueSet.of(args[1].value as Int)
return ValueSet.of((args[1] as IntArg).value)
}
}
@ -101,7 +101,7 @@ private class RegisterValueFinder {
OP_ADDI.code -> {
if (args[0].value == register) {
val prevVals = find(LinkedHashSet(path), block, i, register)
prevVals += args[1].value as Int
prevVals += (args[1] as IntArg).value
return prevVals
}
}
@ -109,7 +109,7 @@ private class RegisterValueFinder {
OP_SUBI.code -> {
if (args[0].value == register) {
val prevVals = find(LinkedHashSet(path), block, i, register)
prevVals -= args[1].value as Int
prevVals -= (args[1] as IntArg).value
return prevVals
}
}
@ -117,7 +117,7 @@ private class RegisterValueFinder {
OP_MULI.code -> {
if (args[0].value == register) {
val prevVals = find(LinkedHashSet(path), block, i, register)
prevVals *= args[1].value as Int
prevVals *= (args[1] as IntArg).value
return prevVals
}
}
@ -125,7 +125,7 @@ private class RegisterValueFinder {
OP_DIVI.code -> {
if (args[0].value == register) {
val prevVals = find(LinkedHashSet(path), block, i, register)
prevVals /= args[1].value as Int
prevVals /= (args[1] as IntArg).value
return prevVals
}
}
@ -155,7 +155,7 @@ private class RegisterValueFinder {
LinkedHashSet(path),
block,
i,
args[0].value as Int
(args[0] as IntArg).value
).minOrNull()!!
val max = max(
@ -163,7 +163,7 @@ private class RegisterValueFinder {
LinkedHashSet(path),
block,
i,
args[0].value as Int + 1
(args[0] as IntArg).value + 1
).maxOrNull()!!,
min + 1,
)
@ -175,8 +175,8 @@ private class RegisterValueFinder {
OP_STACK_PUSHM.code,
OP_STACK_POPM.code,
-> {
val minReg = args[0].value as Int
val maxReg = args[0].value as Int + args[1].value as Int
val minReg = (args[0] as IntArg).value
val maxReg = (args[0] as IntArg).value + (args[1] as IntArg).value
if (register in minReg until maxReg) {
return ValueSet.all()
@ -192,7 +192,7 @@ private class RegisterValueFinder {
val param = params[j]
if (param.type is RegType && param.type.registers != null) {
val regRef = args[j].value as Int
val regRef = (args[j] as IntArg).value
for ((k, regParam) in param.type.registers.withIndex()) {
if (regParam.write && regRef + k == register) {
@ -274,14 +274,15 @@ private class RegisterValueFinder {
return if (register in 1..stack.size) {
val instruction = stack[register - 1]
val value = instruction.args.first().value
val arg = instruction.args.first()
when (instruction.opcode.code) {
OP_ARG_PUSHR.code -> find(LinkedHashSet(path), block, vaStartIdx, value as Int)
OP_ARG_PUSHR.code ->
find(LinkedHashSet(path), block, vaStartIdx, (arg as IntArg).value)
OP_ARG_PUSHL.code,
OP_ARG_PUSHB.code,
OP_ARG_PUSHW.code -> ValueSet.of(value as Int)
OP_ARG_PUSHW.code -> ValueSet.of((arg as IntArg).value)
// TODO: Deal with strings.
else -> ValueSet.all() // String or pointer

View File

@ -51,7 +51,7 @@ private class StackValueFinder {
when (instruction.opcode.code) {
OP_ARG_PUSHR.code -> {
if (pos == 0) {
return getRegisterValue(cfg, instruction, args[0].value as Int)
return getRegisterValue(cfg, instruction, (args[0] as IntArg).value)
} else {
pos--
}
@ -62,7 +62,7 @@ private class StackValueFinder {
OP_ARG_PUSHW.code,
-> {
if (pos == 0) {
return ValueSet.of(args[0].value as Int)
return ValueSet.of((args[0] as IntArg).value)
} else {
pos--
}

View File

@ -242,8 +242,9 @@ private fun findAndParseSegments(
// Never on the stack.
// Eat all remaining arguments.
while (i < instruction.args.size) {
newLabels[instruction.args[i].value as Int] =
newLabels[(instruction.args[i] as IntArg).value] =
SegmentType.Instructions
i++
}
}
@ -347,7 +348,7 @@ private fun getArgLabelValues(
return true
}
} else {
val value = instruction.args[paramIdx].value as Int
val value = (instruction.args[paramIdx] as IntArg).value
val oldType = labels[value]
if (
@ -466,13 +467,13 @@ private fun parseInstructionsSegment(
// Parse the arguments.
try {
val args = parseInstructionArguments(cursor, opcode, dcGcFormat)
instructions.add(Instruction(opcode, args, srcLoc = null))
instructions.add(Instruction(opcode, args, srcLoc = null, valid = true))
} catch (e: Exception) {
if (lenient) {
logger.error(e) {
"Exception occurred while parsing arguments for instruction ${opcode.mnemonic}."
}
instructions.add(Instruction(opcode, emptyList(), srcLoc = null))
instructions.add(Instruction(opcode, emptyList(), srcLoc = null, valid = false))
} else {
throw e
}
@ -565,33 +566,33 @@ private fun parseInstructionArguments(
for (param in opcode.params) {
when (param.type) {
is ByteType ->
args.add(Arg(cursor.uByte().toInt()))
args.add(IntArg(cursor.uByte().toInt()))
is ShortType ->
args.add(Arg(cursor.uShort().toInt()))
args.add(IntArg(cursor.uShort().toInt()))
is IntType ->
args.add(Arg(cursor.int()))
args.add(IntArg(cursor.int()))
is FloatType ->
args.add(Arg(cursor.float()))
args.add(FloatArg(cursor.float()))
// Ensure this case is before the LabelType case because ILabelVarType extends
// LabelType.
is ILabelVarType -> {
varargCount++
val argSize = cursor.uByte()
args.addAll(cursor.uShortArray(argSize.toInt()).map { Arg(it.toInt()) })
args.addAll(cursor.uShortArray(argSize.toInt()).map { IntArg(it.toInt()) })
}
is LabelType -> {
args.add(Arg(cursor.uShort().toInt()))
args.add(IntArg(cursor.uShort().toInt()))
}
is StringType -> {
val maxBytes = min(4096, cursor.bytesLeft)
args.add(
Arg(
StringArg(
if (dcGcFormat) {
cursor.stringAscii(
maxBytes,
@ -610,13 +611,13 @@ private fun parseInstructionArguments(
}
is RegType -> {
args.add(Arg(cursor.uByte().toInt()))
args.add(IntArg(cursor.uByte().toInt()))
}
is RegVarType -> {
varargCount++
val argSize = cursor.uByte()
args.addAll(cursor.uByteArray(argSize.toInt()).map { Arg(it.toInt()) })
args.addAll(cursor.uByteArray(argSize.toInt()).map { IntArg(it.toInt()) })
}
else -> error("Parameter type ${param.type} not implemented.")
@ -725,7 +726,7 @@ private fun tryParseInstructionsSegment(
for (arg in inst.getArgs(index)) {
labelCount++
if (!labelHolder.hasLabel(arg.value as Int)) {
if (!labelHolder.hasLabel((arg as IntArg).value)) {
unknownLabelCount++
}
}
@ -805,34 +806,34 @@ fun writeBytecode(bytecodeIr: BytecodeIr, dcGcFormat: Boolean): BytecodeAndLabel
}
when (param.type) {
ByteType -> cursor.writeByte((arg.value as Int).toByte())
ShortType -> cursor.writeShort((arg.value as Int).toShort())
IntType -> cursor.writeInt(arg.value as Int)
FloatType -> cursor.writeFloat(arg.value as Float)
ByteType -> cursor.writeByte(arg.coerceInt().toByte())
ShortType -> cursor.writeShort(arg.coerceInt().toShort())
IntType -> cursor.writeInt(arg.coerceInt())
FloatType -> cursor.writeFloat(arg.coerceFloat())
// Ensure this case is before the LabelType case because
// ILabelVarType extends LabelType.
ILabelVarType -> {
cursor.writeByte(args.size.toByte())
for (a in args) {
cursor.writeShort((a.value as Int).toShort())
cursor.writeShort(a.coerceInt().toShort())
}
}
is LabelType -> cursor.writeShort((arg.value as Int).toShort())
is LabelType -> cursor.writeShort(arg.coerceInt().toShort())
StringType -> {
val str = arg.value as String
val str = arg.coerceString()
if (dcGcFormat) cursor.writeStringAscii(str, str.length + 1)
else cursor.writeStringUtf16(str, 2 * str.length + 2)
}
is RegType -> {
cursor.writeByte((arg.value as Int).toByte())
cursor.writeByte(arg.coerceInt().toByte())
}
RegVarType -> {
cursor.writeByte(args.size.toByte())
for (a in args) {
cursor.writeByte((a.value as Int).toByte())
cursor.writeByte(a.coerceInt().toByte())
}
}
else -> error(

View File

@ -34,16 +34,17 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_SET_EPISODE,
args = listOf(Arg(0)),
args = listOf(IntArg(0)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 11),
args = listOf(ArgSrcLoc(SrcLoc(2, 17, 1), SrcLoc(2, 16, 2))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_BB_MAP_DESIGNATE,
args = listOf(Arg(1), Arg(2), Arg(3), Arg(4)),
args = listOf(IntArg(1), IntArg(2), IntArg(3), IntArg(4)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(3, 5, 16),
args = listOf(
@ -52,38 +53,42 @@ class AssemblyTests : LibTestSuite {
ArgSrcLoc(SrcLoc(3, 28, 1), SrcLoc(3, 27, 3)),
ArgSrcLoc(SrcLoc(3, 31, 1), SrcLoc(3, 30, 2)),
),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_ARG_PUSHL,
args = listOf(Arg(0)),
args = listOf(IntArg(0)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(4, 23, 1), SrcLoc(4, 22, 3))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_ARG_PUSHW,
args = listOf(Arg(150)),
args = listOf(IntArg(150)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(4, 26, 3), SrcLoc(4, 25, 4))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_SET_FLOOR_HANDLER,
args = emptyList(),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(4, 5, 17),
args = emptyList(),
stackArgs = listOf(
args = listOf(
ArgSrcLoc(SrcLoc(4, 23, 1), SrcLoc(4, 22, 3)),
ArgSrcLoc(SrcLoc(4, 26, 3), SrcLoc(4, 25, 4)),
),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_RET,
@ -91,8 +96,9 @@ class AssemblyTests : LibTestSuite {
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(5, 5, 3),
args = emptyList(),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
),
srcLoc = SegmentSrcLoc(labels = mutableListOf(SrcLoc(1, 1, 2))),
@ -102,23 +108,25 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_ARG_PUSHL,
args = listOf(Arg(1)),
args = listOf(IntArg(1)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(7, 18, 1), SrcLoc(7, 17, 2))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_SET_MAINWARP,
args = emptyList(),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(7, 5, 12),
args = emptyList(),
stackArgs = listOf(
args = listOf(
ArgSrcLoc(SrcLoc(7, 18, 1), SrcLoc(7, 17, 2)),
),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_RET,
@ -126,8 +134,9 @@ class AssemblyTests : LibTestSuite {
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(8, 5, 3),
args = emptyList(),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
),
srcLoc = SegmentSrcLoc(labels = mutableListOf(SrcLoc(6, 1, 4))),
@ -160,35 +169,38 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_LETI,
args = listOf(Arg(255), Arg(7)),
args = listOf(IntArg(255), IntArg(7)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 4),
args = listOf(
ArgSrcLoc(SrcLoc(2, 10, 4), SrcLoc(2, 9, 6)),
ArgSrcLoc(SrcLoc(2, 16, 1), SrcLoc(2, 15, 2)),
),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_ARG_PUSHR,
args = listOf(Arg(255)),
args = listOf(IntArg(255)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(3, 10, 4), SrcLoc(3, 9, 5))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_EXIT,
args = emptyList(),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(3, 5, 4),
args = emptyList(),
stackArgs = listOf(
args = listOf(
ArgSrcLoc(SrcLoc(3, 10, 4), SrcLoc(3, 9, 5)),
),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_RET,
@ -196,8 +208,9 @@ class AssemblyTests : LibTestSuite {
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(4, 5, 3),
args = emptyList(),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
),
srcLoc = SegmentSrcLoc(
@ -231,33 +244,36 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_ARG_PUSHB,
args = listOf(Arg(200)),
args = listOf(IntArg(200)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(2, 15, 4), SrcLoc(2, 14, 6))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_ARG_PUSHL,
args = listOf(Arg(3)),
args = listOf(IntArg(3)),
srcLoc = InstructionSrcLoc(
mnemonic = null,
args = listOf(ArgSrcLoc(SrcLoc(2, 21, 1), SrcLoc(2, 20, 2))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_P_DEAD_V3,
args = emptyList(),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 9),
args = emptyList(),
stackArgs = listOf(
args = listOf(
ArgSrcLoc(SrcLoc(2, 15, 4), SrcLoc(2, 14, 6)),
ArgSrcLoc(SrcLoc(2, 21, 1), SrcLoc(2, 20, 2)),
),
trailingArgSeparator = false,
),
valid = true,
),
Instruction(
opcode = OP_RET,
@ -265,8 +281,9 @@ class AssemblyTests : LibTestSuite {
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(3, 5, 3),
args = emptyList(),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = true,
),
),
srcLoc = SegmentSrcLoc(
@ -289,6 +306,7 @@ class AssemblyTests : LibTestSuite {
)
assertTrue(result is Success)
// Bytecode contains one invalid instruction.
assertDeepEquals(
BytecodeIr(
listOf(
@ -297,12 +315,13 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_RET,
args = emptyList(),
args = listOf(IntArg(100)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 3),
args = emptyList(),
stackArgs = emptyList(),
args = listOf(ArgSrcLoc(SrcLoc(2, 9, 3), SrcLoc(2, 8, 4))),
trailingArgSeparator = false,
),
valid = false,
),
),
srcLoc = SegmentSrcLoc(
@ -333,14 +352,24 @@ class AssemblyTests : LibTestSuite {
)
assertTrue(result is Success)
// Bytecode contains no instructions.
// Bytecode contains one invalid instruction.
assertDeepEquals(
BytecodeIr(
listOf(
InstructionSegment(
labels = mutableListOf(5000),
instructions = mutableListOf(),
instructions = mutableListOf(
Instruction(
opcode = OP_LETI,
args = listOf(IntArg(100)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 4),
args = listOf(ArgSrcLoc(SrcLoc(2, 10, 4), SrcLoc(2, 9, 5))),
trailingArgSeparator = false,
),
valid = false,
),
),
srcLoc = SegmentSrcLoc(
labels = mutableListOf(SrcLoc(1, 1, 5)),
),
@ -370,7 +399,7 @@ class AssemblyTests : LibTestSuite {
assertTrue(result is Success)
// Bytecode contains an instruction, since it's technically valid.
// Bytecode contains one invalid instruction.
assertDeepEquals(
BytecodeIr(
listOf(
@ -379,12 +408,13 @@ class AssemblyTests : LibTestSuite {
instructions = mutableListOf(
Instruction(
opcode = OP_SWITCH_JMP,
args = listOf(Arg(100)),
args = listOf(IntArg(100)),
srcLoc = InstructionSrcLoc(
mnemonic = SrcLoc(2, 5, 10),
args = listOf(ArgSrcLoc(SrcLoc(2, 16, 4), SrcLoc(2, 15, 5))),
stackArgs = emptyList(),
trailingArgSeparator = false,
),
valid = false,
),
),
srcLoc = SegmentSrcLoc(

View File

@ -15,17 +15,19 @@ class DisassemblyTests : LibTestSuite {
Instruction(
opcode = OP_SWITCH_JMP,
args = listOf(
Arg(90),
Arg(100),
Arg(101),
Arg(102),
IntArg(90),
IntArg(100),
IntArg(101),
IntArg(102),
),
srcLoc = null,
valid = true,
),
Instruction(
opcode = OP_RET,
args = emptyList(),
srcLoc = null,
valid = true,
),
),
)
@ -57,26 +59,31 @@ class DisassemblyTests : LibTestSuite {
opcode = OP_VA_START,
args = emptyList(),
srcLoc = null,
valid = true,
),
Instruction(
opcode = OP_ARG_PUSHW,
args = listOf(Arg(1337)),
args = listOf(IntArg(1337)),
srcLoc = null,
valid = true,
),
Instruction(
opcode = OP_VA_CALL,
args = listOf(Arg(100)),
args = listOf(IntArg(100)),
srcLoc = null,
valid = true,
),
Instruction(
opcode = OP_VA_END,
args = emptyList(),
srcLoc = null,
valid = true,
),
Instruction(
opcode = OP_RET,
args = emptyList(),
srcLoc = null,
valid = true,
),
),
)

View File

@ -3,6 +3,7 @@ package world.phantasmal.lib.asm
import world.phantasmal.lib.test.LibTestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class OpcodeTests : LibTestSuite {
@ -28,10 +29,13 @@ class OpcodeTests : LibTestSuite {
assertTrue(!hasVarargs || opcode.params.lastOrNull()?.varargs == true)
assertEquals(hasVarargs, opcode.varargs)
// Register references.
for (param in opcode.params) {
val type = param.type
// Any should only be used with register parameters.
assertNotEquals(AnyType.Instance, type)
// Register references.
if (type is RegType) {
val registers = type.registers

View File

@ -61,6 +61,7 @@ fun assertDeepEquals(
message: String? = null,
) {
assertEquals(expected.opcode, actual.opcode, message)
assertEquals(expected.valid, actual.valid, message)
assertDeepEquals(expected.args, actual.args, ::assertEquals, message)
if (!ignoreSrcLocs) {
@ -91,9 +92,9 @@ fun assertDeepEquals(
}
assertNotNull(actual, message)
assertEquals(expected.trailingArgSeparator, actual.trailingArgSeparator, message)
assertDeepEquals(expected.mnemonic, actual.mnemonic, message)
assertDeepEquals(expected.args, actual.args, ::assertDeepEquals, message)
assertDeepEquals(expected.stackArgs, actual.stackArgs, ::assertDeepEquals, message)
}
fun assertDeepEquals(expected: ArgSrcLoc?, actual: ArgSrcLoc?, message: String? = null) {

View File

@ -7,6 +7,7 @@ import world.phantasmal.lib.asm.dataFlowAnalysis.getMapDesignations
import world.phantasmal.lib.asm.dataFlowAnalysis.getStackValue
import world.phantasmal.web.shared.messages.*
import world.phantasmal.web.shared.messages.AssemblyProblem
import kotlin.math.max
import kotlin.math.min
import world.phantasmal.lib.asm.AssemblyProblem as LibAssemblyProblem
@ -197,9 +198,9 @@ class AsmAnalyser {
var signature: Signature? = null
var activeParam = -1
getInstructionForSrcLoc(lineNo, col)?.let { (inst, argIdx) ->
getInstructionForSrcLoc(lineNo, col)?.let { (inst, paramIdx) ->
signature = getSignature(inst.opcode)
activeParam = argIdx
activeParam = paramIdx
}
return signature?.let { sig ->
@ -275,14 +276,14 @@ class AsmAnalyser {
val srcLoc = argSrcLocs[i].coarse
if (positionInside(lineNo, col, srcLoc)) {
val label = arg.value as Int
val label = (arg as IntArg).value
result = getLabelDefinitions(label)
break@loop
}
}
} else {
// Stack arguments.
val argSrcLocs = inst.getStackArgSrcLocs(paramIdx)
val argSrcLocs = inst.getArgSrcLocs(paramIdx)
for ((i, argSrcLoc) in argSrcLocs.withIndex()) {
if (positionInside(lineNo, col, argSrcLoc.coarse)) {
@ -332,7 +333,7 @@ class AsmAnalyser {
is Ir.Inst -> {
val srcLoc = ir.inst.srcLoc?.mnemonic
if (ir.argIdx == -1 ||
if (ir.paramIdx == -1 ||
// Also return this instruction if we're right past the mnemonic. E.g. at the
// first whitespace character preceding the first argument.
(srcLoc != null && col <= srcLoc.col + srcLoc.len)
@ -373,7 +374,7 @@ class AsmAnalyser {
lastCol = mnemonicSrcLoc.col + mnemonicSrcLoc.len
if (positionInside(lineNo, col, mnemonicSrcLoc)) {
return Ir.Inst(inst, argIdx = -1)
return Ir.Inst(inst, paramIdx = -1)
}
}
@ -386,26 +387,13 @@ class AsmAnalyser {
}
}
if (inlineStackArgs) {
for ((argIdx, argSrcLoc) in srcLoc.stackArgs.withIndex()) {
instLineNo = argSrcLoc.coarse.lineNo
lastCol = argSrcLoc.coarse.col + argSrcLoc.coarse.len
if (positionInside(lineNo, col, argSrcLoc.coarse)) {
return Ir.Inst(inst, argIdx)
}
}
}
if (lineNo == instLineNo && col >= lastCol) {
return Ir.Inst(
inst,
if (inlineStackArgs && inst.opcode.stack === StackInteraction.Pop) {
srcLoc.stackArgs.lastIndex
} else {
srcLoc.args.lastIndex
},
)
val argIdx = max(0, srcLoc.args.lastIndex) +
(if (srcLoc.trailingArgSeparator) 1 else 0)
val paramIdx = min(argIdx, inst.opcode.params.lastIndex)
return Ir.Inst(inst, paramIdx)
}
}
}
@ -434,7 +422,7 @@ class AsmAnalyser {
)
private sealed class Ir {
data class Inst(val inst: Instruction, val argIdx: Int) : Ir()
data class Inst(val inst: Instruction, val paramIdx: Int) : Ir()
}
companion object {

View File

@ -53,6 +53,80 @@ class AsmAnalyserTests : AssemblyWorkerTestSuite() {
}
}
@Test
fun getSignatureHelp_incomplete_instruction() = test {
// Three kinds of incomplete instructions.
val analyser = createAsmAnalyser(
"""
.code
0:
leti
leti r0
leti r0,
""".trimIndent()
)
val requestId = 113
for (col in 1..4) {
val response = analyser.getSignatureHelp(requestId, lineNo = 3, col)
assertDeepEquals(Response.GetSignatureHelp(requestId, null), response)
}
fun sigHelp(activeParameter: Int) = Response.GetSignatureHelp(
requestId,
SignatureHelp(
Signature(
label = "leti Reg<out Int>, Int",
documentation = "Sets a register to the given value.",
listOf(
Parameter(labelStart = 5, labelEnd = 17, null),
Parameter(labelStart = 19, labelEnd = 22, null),
),
),
activeParameter,
),
)
// First instruction: leti
for ((colRange, sigHelp) in listOf(
5..8 to sigHelp(-1),
9..9 to sigHelp(0),
)) {
for (col in colRange) {
val response = analyser.getSignatureHelp(requestId, 3, col)
assertDeepEquals(sigHelp, response, "col = $col")
}
}
// Second instruction: leti r0
for ((colRange, sigHelp) in listOf(
5..8 to sigHelp(-1),
9..12 to sigHelp(0),
)) {
for (col in colRange) {
val response = analyser.getSignatureHelp(requestId, 4, col)
assertDeepEquals(sigHelp, response, "col = $col")
}
}
// Third instruction: leti r0,
for ((colRange, sigHelp) in listOf(
5..8 to sigHelp(-1),
9..12 to sigHelp(0),
13..13 to sigHelp(1),
)) {
for (col in colRange) {
val response = analyser.getSignatureHelp(requestId, 5, col)
assertDeepEquals(sigHelp, response, "col = $col")
}
}
}
@Test
fun getHighlights_for_instruction() = test {
val analyser = createAsmAnalyser(

View File

@ -3,7 +3,10 @@ package world.phantasmal.web.core.rendering.conversion
import mu.KotlinLogging
import org.khronos.webgl.Float32Array
import org.khronos.webgl.Uint16Array
import world.phantasmal.core.*
import world.phantasmal.core.JsArray
import world.phantasmal.core.asArray
import world.phantasmal.core.isBitSet
import world.phantasmal.core.jsArrayOf
import world.phantasmal.core.unsafe.UnsafeMap
import world.phantasmal.lib.fileFormats.*
import world.phantasmal.lib.fileFormats.ninja.*
@ -11,6 +14,21 @@ import world.phantasmal.web.core.dot
import world.phantasmal.web.core.toQuaternion
import world.phantasmal.web.externals.three.*
import world.phantasmal.webui.obj
import kotlin.collections.List
import kotlin.collections.MutableList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.forEach
import kotlin.collections.forEachIndexed
import kotlin.collections.getOrPut
import kotlin.collections.indices
import kotlin.collections.iterator
import kotlin.collections.last
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.sum
import kotlin.collections.withIndex
private val logger = KotlinLogging.logger {}
@ -200,7 +218,7 @@ fun AreaObject.fingerPrint(): String =
append('_')
append(meshCount.toString(36))
append('_')
append(radius.reinterpretAsUInt().toString(36))
append(radius.toRawBits().toUInt().toString(36))
}
private fun areaObjectToMesh(

View File

@ -68,7 +68,7 @@ class TestComponents(private val ctx: TestContext) {
// Rendering
var createThreeRenderer: (HTMLCanvasElement) -> DisposableThreeRenderer by default {
{ _ ->
{
object : DisposableThreeRenderer {
override val renderer = NopRenderer().unsafeCast<WebGLRenderer>()
override fun dispose() {}
@ -76,15 +76,7 @@ class TestComponents(private val ctx: TestContext) {
}
}
private fun <T> default(defaultValue: () -> T) = LazyDefault {
val value = defaultValue()
if (value is Disposable) {
ctx.disposer.add(value)
}
value
}
private fun <T> default(defaultValue: () -> T) = LazyDefault(defaultValue)
private inner class LazyDefault<T>(private val defaultValue: () -> T) {
private var initialized = false