Skip to content

Commit 3ecc6dc

Browse files
committed
Adds bytecode constants and utilities
1 parent 1b68d1e commit 3ecc6dc

8 files changed

Lines changed: 1189 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.amazon.ion._private_
2+
3+
/**
4+
* Suppress individual Spotbugs warnings.
5+
*/
6+
annotation class SuppressFBWarnings(val value: Array<String>, val justification: String)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.amazon.ion.bytecode.ir
4+
5+
import com.amazon.ion._private_.SuppressFBWarnings
6+
import java.util.function.Consumer
7+
8+
/**
9+
* Contains static functions for dumping a string representation of the bytecode instructions.
10+
*
11+
* E.g.:
12+
* ```text
13+
* L0 STRUCT_START L=6
14+
* L1 . FIELD_NAME_SID $1
15+
* L2 . LIST_START L=3
16+
* L3 . . INT_I16 1
17+
* L4 . . INT_I16 2
18+
* L5 . END_CONTAINER
19+
* L6 END_CONTAINER
20+
* L7 END_OF_INPUT
21+
* ```
22+
*
23+
* This is not performance-optimized at all. Do not use in any happy-path code.
24+
*
25+
* This is intended to support testing [BytecodeGenerator]s and to be used as a renderer in a debugger.
26+
* If we ever choose to expose some sort of debugging utility in the ion-java-cli, it could end up being used there too.
27+
*/
28+
@SuppressFBWarnings(
29+
value = ["SF_SWITCH_NO_DEFAULT"],
30+
justification = "Some of the 'when' expressions have 'else' cases with empty bodies, so kotlin doesn't include the default case in the generated bytecode."
31+
)
32+
internal object Debugger {
33+
34+
/**
35+
* Helper function to render bytecode as an `Array<String>` to make it easier to read in the IntelliJ debugger.
36+
*/
37+
@JvmStatic
38+
fun renderBytecodeToArray(bytecode: IntArray): Array<String> {
39+
val sb = StringBuilder()
40+
renderBytecodeToString(bytecode, sb::append, useIndent = true, useNumbers = false)
41+
return sb.toString().trim().split("\n").toTypedArray()
42+
}
43+
44+
@JvmStatic
45+
private fun line(n: Int): String {
46+
return "L$n".padEnd(6, ' ')
47+
}
48+
49+
@JvmStatic
50+
private operator fun Consumer<String>.invoke(value: String) = accept(value)
51+
52+
/**
53+
* Writes out some bytecode as a string to the [write] callback function.
54+
* See [Debugger] documentation and tests for example output.
55+
*/
56+
@OptIn(ExperimentalStdlibApi::class)
57+
@JvmStatic
58+
fun renderBytecodeToString(
59+
bytecode: IntArray,
60+
/** The destination for the debug output; default is stdout. */
61+
write: Consumer<String> = Consumer { print(it) },
62+
/** Optionally, provide a constant pool to have some supplemental information added for CP_INDEX instructions */
63+
constantPool: Array<Any?>? = null,
64+
/** Optionally, provide a symbol table to have some supplemental information added for SID instructions */
65+
symbolTable: Array<String?>? = null,
66+
start: Int = 0,
67+
end: Int = bytecode.size,
68+
/** Should instructions be indented in accordance the to data model depth? */
69+
useIndent: Boolean = true,
70+
/** Should each line begin with a label indicating the instruction number? */
71+
useNumbers: Boolean = true,
72+
/** Should it throw an exception for an unrecognized instruction? */
73+
strict: Boolean = false,
74+
) {
75+
var indent = ""
76+
var i = start
77+
while (i < end) {
78+
if (useNumbers) write(line(i))
79+
80+
val instruction = bytecode[i++]
81+
val operationInt = Instructions.toOperation(instruction)
82+
val instructionInfo = InstructionInfo.entries.singleOrNull { it.operation == operationInt }
83+
84+
instructionInfo ?: if (strict) {
85+
throw IllegalStateException("Unknown operation $operationInt at position ${i - 1}.")
86+
} else {
87+
write(indent)
88+
write("UNKNOWN ")
89+
write(instruction.toHexString())
90+
write("\n")
91+
continue
92+
}
93+
94+
// Decrease the indent, if necessary.
95+
if (useIndent) when (instructionInfo) {
96+
InstructionInfo.END_CONTAINER -> indent = indent.dropLast(2)
97+
else -> { /* do nothing */ }
98+
}
99+
100+
// Write the operation name, and any data carried in the instruction
101+
write(indent)
102+
write(instructionInfo.name)
103+
write(" ")
104+
write(instructionInfo.dataType.formatter(Instructions.getData(instruction)).toString())
105+
106+
// If we have symbol table or constant pool available, add in supplemental information
107+
if (constantPool != null && instructionInfo.dataType == InstructionInfo.DataInfo.CP_INDEX) {
108+
val cpIndex = Instructions.getData(instruction)
109+
if (cpIndex >= constantPool.size) {
110+
write(" ERROR: missing constant $cpIndex")
111+
} else {
112+
write(" <${constantPool[cpIndex].toString().take(20)}>")
113+
}
114+
}
115+
if (symbolTable != null && instructionInfo.dataType == InstructionInfo.DataInfo.SID) {
116+
val sid = Instructions.getData(instruction)
117+
if (sid == 0) {
118+
write(" <$0>")
119+
} else if (sid >= symbolTable.size) {
120+
write(" ERROR: symbol out of bounds $sid")
121+
} else {
122+
write(" <${symbolTable[sid]?.take(20) ?: "$0"}>")
123+
}
124+
}
125+
126+
// Write out the operands
127+
val operandInfo = instructionInfo.operands
128+
when (operandInfo.n) {
129+
1 -> {
130+
write("\n")
131+
if (useNumbers) write(line(i))
132+
val operand = bytecode[i++]
133+
write("$indent └─ <${operand.toHexString()}> ${operandInfo.formatter1Int(operand)}".trimEnd())
134+
}
135+
2 -> {
136+
write("\n")
137+
if (useNumbers) write(line(i))
138+
val operand1 = bytecode[i++]
139+
write("$indent ├─ <${operand1.toHexString()}> ${operandInfo.formatter1Int(operand1)}".trimEnd())
140+
write("\n")
141+
if (useNumbers) write(line(i))
142+
val operand2 = bytecode[i++]
143+
write("$indent └─ <${operand2.toHexString()}> ${operandInfo.formatter2Int(operand1, operand2)}".trimEnd())
144+
}
145+
else -> { /* do nothing */ }
146+
}
147+
write("\n")
148+
149+
// Adjust indent, if needed.
150+
if (useIndent) when (instructionInfo) {
151+
InstructionInfo.DIRECTIVE_SET_SYMBOLS,
152+
InstructionInfo.DIRECTIVE_ADD_SYMBOLS,
153+
InstructionInfo.DIRECTIVE_SET_MACROS,
154+
InstructionInfo.DIRECTIVE_ADD_MACROS,
155+
InstructionInfo.DIRECTIVE_USE,
156+
InstructionInfo.DIRECTIVE_MODULE,
157+
InstructionInfo.DIRECTIVE_ENCODING,
158+
InstructionInfo.LIST_START,
159+
InstructionInfo.SEXP_START,
160+
InstructionInfo.STRUCT_START -> indent += ". "
161+
else -> { /* No need to increase the indent */ }
162+
}
163+
164+
when (instructionInfo) {
165+
InstructionInfo.REFILL,
166+
InstructionInfo.END_OF_INPUT -> break
167+
else -> continue
168+
}
169+
}
170+
}
171+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.amazon.ion.bytecode.ir
4+
5+
/**
6+
* Enumeration of all supported bytecode instructions with their metadata.
7+
*
8+
* Each instruction entry contains the operation code, data type information,
9+
* and operand requirements. This enum serves as the central registry for
10+
* instruction definitions used in Ion bytecode generation and execution.
11+
*
12+
* See `com/amazon/ion/bytecode/ir/instruction_reference.md` for more details about the instruction set.
13+
*
14+
* @property operation The operation code identifier for this instruction
15+
* @property dataType Information about the data format and encoding for this instruction
16+
* @property operands Information about the operands this instruction expects
17+
*/
18+
internal enum class InstructionInfo(
19+
val operation: Int,
20+
val dataType: DataInfo,
21+
val operands: OperandInfo = OperandInfo.NO_OPERANDS,
22+
) {
23+
NULL_NULL(Operation.OP_NULL_NULL, DataInfo.NO_DATA),
24+
BOOL(Operation.OP_BOOL, DataInfo.BOOLEAN),
25+
NULL_BOOL(Operation.OP_NULL_BOOL, DataInfo.NO_DATA),
26+
INT_I16(Operation.OP_INT_I16, DataInfo.I16),
27+
INT_I32(Operation.OP_INT_I32, DataInfo.NO_DATA, OperandInfo.I32),
28+
INT_I64(Operation.OP_INT_I64, DataInfo.NO_DATA, OperandInfo.I64),
29+
INT_CP(Operation.OP_INT_CP, DataInfo.CP_INDEX),
30+
INT_REF(Operation.OP_INT_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
31+
NULL_INT(Operation.OP_NULL_INT, DataInfo.NO_DATA),
32+
FLOAT_F32(Operation.OP_FLOAT_F32, DataInfo.NO_DATA, OperandInfo.F32),
33+
FLOAT_F64(Operation.OP_FLOAT_F64, DataInfo.NO_DATA, OperandInfo.F64),
34+
NULL_FLOAT(Operation.OP_NULL_FLOAT, DataInfo.NO_DATA),
35+
DECIMAL_CP(Operation.OP_DECIMAL_CP, DataInfo.CP_INDEX),
36+
DECIMAL_REF(Operation.OP_DECIMAL_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
37+
NULL_DECIMAL(Operation.OP_NULL_DECIMAL, DataInfo.NO_DATA),
38+
TIMESTAMP_CP(Operation.OP_TIMESTAMP_CP, DataInfo.CP_INDEX),
39+
SHORT_TIMESTAMP_REF(Operation.OP_SHORT_TIMESTAMP_REF, DataInfo.OPCODE, OperandInfo.OFFSET),
40+
TIMESTAMP_REF(Operation.OP_TIMESTAMP_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
41+
NULL_TIMESTAMP(Operation.OP_NULL_TIMESTAMP, DataInfo.NO_DATA),
42+
STRING_CP(Operation.OP_STRING_CP, DataInfo.CP_INDEX),
43+
STRING_REF(Operation.OP_STRING_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
44+
NULL_STRING(Operation.OP_NULL_STRING, DataInfo.NO_DATA),
45+
SYMBOL_CP(Operation.OP_SYMBOL_CP, DataInfo.CP_INDEX),
46+
SYMBOL_REF(Operation.OP_SYMBOL_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
47+
SYMBOL_SID(Operation.OP_SYMBOL_SID, DataInfo.SID),
48+
SYMBOL_CHAR(Operation.OP_SYMBOL_CHAR, DataInfo.CHAR),
49+
NULL_SYMBOL(Operation.OP_NULL_SYMBOL, DataInfo.NO_DATA),
50+
BLOB_CP(Operation.OP_BLOB_CP, DataInfo.CP_INDEX),
51+
BLOB_REF(Operation.OP_BLOB_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
52+
NULL_BLOB(Operation.OP_NULL_BLOB, DataInfo.NO_DATA),
53+
CLOB_CP(Operation.OP_CLOB_CP, DataInfo.CP_INDEX),
54+
CLOB_REF(Operation.OP_CLOB_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
55+
NULL_CLOB(Operation.OP_NULL_CLOB, DataInfo.NO_DATA),
56+
LIST_START(Operation.OP_LIST_START, DataInfo.BYTECODE_LENGTH),
57+
NULL_LIST(Operation.OP_NULL_LIST, DataInfo.NO_DATA),
58+
SEXP_START(Operation.OP_SEXP_START, DataInfo.BYTECODE_LENGTH),
59+
NULL_SEXP(Operation.OP_NULL_SEXP, DataInfo.NO_DATA),
60+
STRUCT_START(Operation.OP_STRUCT_START, DataInfo.BYTECODE_LENGTH),
61+
NULL_STRUCT(Operation.OP_NULL_STRUCT, DataInfo.NO_DATA),
62+
FIELD_NAME_CP(Operation.OP_FIELD_NAME_CP, DataInfo.CP_INDEX),
63+
FIELD_NAME_REF(Operation.OP_FIELD_NAME_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
64+
FIELD_NAME_SID(Operation.OP_FIELD_NAME_SID, DataInfo.SID),
65+
ANNOTATION_CP(Operation.OP_ANNOTATION_CP, DataInfo.CP_INDEX),
66+
ANNOTATION_REF(Operation.OP_ANNOTATION_REF, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
67+
ANNOTATION_SID(Operation.OP_ANNOTATION_SID, DataInfo.SID),
68+
PLACEHOLDER(Operation.OP_PLACEHOLDER, DataInfo.NO_DATA),
69+
PLACEHOLDER_OPT(Operation.OP_PLACEHOLDER_OPT, DataInfo.BYTECODE_LENGTH),
70+
PLACEHOLDER_TAGLESS(Operation.OP_PLACEHOLDER_TAGLESS, DataInfo.OPCODE),
71+
ARGUMENT_NONE(Operation.OP_ARGUMENT_NONE, DataInfo.NO_DATA),
72+
IVM(Operation.OP_IVM, DataInfo.IVM),
73+
DIRECTIVE_SET_SYMBOLS(Operation.OP_DIRECTIVE_SET_SYMBOLS, DataInfo.NO_DATA),
74+
DIRECTIVE_ADD_SYMBOLS(Operation.OP_DIRECTIVE_ADD_SYMBOLS, DataInfo.NO_DATA),
75+
DIRECTIVE_SET_MACROS(Operation.OP_DIRECTIVE_SET_MACROS, DataInfo.NO_DATA),
76+
DIRECTIVE_ADD_MACROS(Operation.OP_DIRECTIVE_ADD_MACROS, DataInfo.NO_DATA),
77+
DIRECTIVE_USE(Operation.OP_DIRECTIVE_USE, DataInfo.NO_DATA),
78+
DIRECTIVE_MODULE(Operation.OP_DIRECTIVE_MODULE, DataInfo.NO_DATA),
79+
DIRECTIVE_ENCODING(Operation.OP_DIRECTIVE_ENCODING, DataInfo.NO_DATA),
80+
INVOKE(Operation.OP_INVOKE, DataInfo.MACRO_ID),
81+
REFILL(Operation.OP_REFILL, DataInfo.NO_DATA),
82+
END_TEMPLATE(Operation.OP_END_TEMPLATE, DataInfo.NO_DATA),
83+
END_OF_INPUT(Operation.OP_END_OF_INPUT, DataInfo.NO_DATA),
84+
END_CONTAINER(Operation.OP_END_CONTAINER, DataInfo.NO_DATA),
85+
META_OFFSET(Operation.OP_META_OFFSET, DataInfo.NO_DATA, OperandInfo.OFFSET),
86+
META_ROWCOL(Operation.OP_META_ROWCOL, DataInfo.NO_DATA, OperandInfo.ROW),
87+
META_COMMENT(Operation.OP_META_COMMENT, DataInfo.REF_LENGTH, OperandInfo.OFFSET),
88+
;
89+
90+
companion object {
91+
/**
92+
* 32-bit bitmask used for extracting the lower 32 bits from a long value.
93+
* Used in operand formatting operations to handle 64-bit values split across two 32-bit integers.
94+
*/
95+
private val BITMASK_32 = 0xFFFFFFFFL
96+
}
97+
98+
/**
99+
* Enumeration defining how instruction data should be formatted for display and debugging.
100+
*
101+
* Each data type has an associated formatter function that converts raw integer data
102+
* into a human-readable representation appropriate for that data type.
103+
*
104+
* @property formatter Function that converts an integer value to its formatted representation
105+
*/
106+
@OptIn(ExperimentalStdlibApi::class)
107+
enum class DataInfo(val formatter: (Int) -> Any) {
108+
/** No data associated with this instruction */
109+
NO_DATA({ "" }),
110+
/** Constant pool index reference */
111+
CP_INDEX(Int::toString),
112+
/** Symbol ID with $ prefix for display */
113+
SID({ "\$$it" }),
114+
/** 16-bit signed integer */
115+
I16(Int::toShort),
116+
/** Single character value */
117+
CHAR(Int::toChar),
118+
/** Boolean value (1 = true, 0 = false) */
119+
BOOLEAN({ it == 1 }),
120+
/** Bytecode length with L= prefix */
121+
BYTECODE_LENGTH({ "L=$it" }),
122+
/** Reference length with L= prefix */
123+
REF_LENGTH({ "L=$it" }),
124+
/** Operation code as hexadecimal byte */
125+
OPCODE({ it.toByte().toHexString() }),
126+
/** Macro identifier */
127+
MACRO_ID({ it }),
128+
/** Ion Version Marker as hexadecimal short */
129+
IVM({ "${it.shr(8)}.${it.and(0xFF)}" }),
130+
/** Column number with col= prefix */
131+
COLUMN({ "col=$it" }),
132+
}
133+
134+
/**
135+
* Enumeration defining operand information for instructions.
136+
*
137+
* Specifies the number of operands an instruction expects and provides
138+
* formatting functions for displaying operand values in human-readable form.
139+
*
140+
* @property n The number of operands this instruction type expects
141+
* @property formatter1Int Formatter for single-operand instructions
142+
* @property formatter2Int Formatter for two-operand instructions
143+
*/
144+
@OptIn(ExperimentalStdlibApi::class)
145+
enum class OperandInfo(
146+
val n: Int,
147+
val formatter1Int: (Int) -> String = { "" },
148+
val formatter2Int: (Int, Int) -> String = { _, _ -> "" }
149+
) {
150+
/** Instruction takes no operands */
151+
NO_OPERANDS(0),
152+
/** 32-bit signed integer operand */
153+
I32(1, { "$it" }),
154+
/** 64-bit signed integer operand (split across two 32-bit values) */
155+
I64(
156+
2,
157+
formatter1Int = { "─┐" },
158+
formatter2Int = { msb, lsb -> "─┴─ " + msb.toLong().shl(32).or(lsb.toLong() and BITMASK_32).toString() }
159+
),
160+
/** 32-bit floating point operand */
161+
F32(1, { Float.fromBits(it).toString() }),
162+
/** 64-bit floating point operand (split across two 32-bit values) */
163+
F64(
164+
2,
165+
formatter1Int = { "─┐" },
166+
formatter2Int = { msb, lsb -> "─┴─ " + Double.fromBits(msb.toLong().shl(32).or(lsb.toLong() and BITMASK_32)).toString() }
167+
),
168+
/** Input offset operand */
169+
OFFSET(1, { "offset=${it.toLong().and(BITMASK_32)}" }),
170+
/** Row number operand for source location tracking */
171+
ROW(1, { "row=$it" })
172+
}
173+
}

0 commit comments

Comments
 (0)