JEP draft: Extended Opcodes

AuthorJulian Waters
TypeInfrastructure
ScopeSE
StatusDraft
Componentspecification / vm
Discussionhotspot dash dev at openjdk dot java dot net
EffortM
Created2022/03/17 02:21
Updated2023/07/28 13:52
Issue8283291

Summary

Implement changes to support a larger list of possible opcodes for the JVM, without breaking any compatibility between classes using the existing 1 byte opcodes. Rename JVM bytecode to "opcode" to signify that JVM instructions are no longer constrained to a single byte.

Goals

This JEP aims to free up more opcodes past the arbitrary one-byte size restriction, allowing space for new instructions be used in future projects and enhancements for the JVM (and consequently modify the JVM Specification to allow for these new instructions, so that implementations are allowed more leeway)

Non-Goals

It is not a goal to replace or change any of the existing opcodes or add any new ones within the scope of this JEP.

Success Metrics

The JVM's performance during execution of any existing or new opcodes during runtime does not degrade when new instructions are added (Such as type support for every const instruction as a generic example), and newer opcodes should be able to be added easily in the runtime without any concern for their storage mechanisms when compiling to JVM opcode.

Motivation

Currently, the Java Virtual Machine is constrained to a single byte of instructions. This leaves a mere 256 opcodes for implementations to work with. Of these, approximately 202 are already in use and 3 are reserved for special use cases, leaving just 55 remaining for use in the future. This places pressure and several restrictions on any work where the instruction set of the JVM potentially has to be modified to allow for new language constructs or optimization attempts at the bytecode level, in several cases forcing alternative and less optimal approaches as the final solution. As such, the JVM would benefit from having more opcodes available for use, allowing for leniency when work that may require the addition of new instructions to HotSpot takes place.

At the same time however, the specification clearly mentions the reason for the choice to keep the opcodes to a byte:

"The decision to limit the Java Virtual Machine opcode to a byte and to forgo data alignment within compiled code reflects a conscious bias in favor of compactness, possibly at the cost of some performance in naive implementations. A one-byte opcode also limits the size of the instruction set. Not assuming data alignment means that immediate data larger than a byte must be constructed from bytes at run time on many machines."

In light of a this, a solution to provide the option to extend JVM opcode should be developed, but otherwise only use the 1 byte long opcodes that already exist.

Description

As was suggested in an internal discussion (and subsequently adopted over the previous approach, which is listed below under alternatives), we'll reserve any one of the currently unused opcodes (0xCB - 0xFD at the time of writing) to mean "extend", signifying to the Java Virtual Machine that the opcode after it is an extended one. This is similar to how the existing wide opcode operates:

wide:

opcode, indexbyte1, indexbyte2 or iinc, indexbyte1, indexbyte2, countbyte1, countbyte2

execute opcode, where opcode is either iload, fload, aload, lload, dload, istore, fstore, astore, lstore, dstore, or ret, but assume the index is 16 bit; or execute iinc, where the index is 16 bits and the constant to increment by is a signed 16 bit short

The opcode directly after the "extend" instruction would be interpreted differently, for example, given the arraylength instruction (0xBE) and an extended imaginary instruction of 0x01BE, the Java Virtual Machine would read 0xBE as arraylength as per normal if the "extend" opcode did not come before it, however if "extend" did come before, it would be read as 0x01BE. An interesting possibility this opens is chaining extend opcodes together, for instance the following sequence of

extend extend extend 0xBE

would effectively be read as 0x03BE. This at the very least frees up 256 new opcodes for use as noted in the discussion, with minimal impact on space or on producers or consumers of opcodes, at the cost of a single one.

Alternatives

Alternatively, we could simply keep the single byte opcodes, and work with their restraints, however this often hinders any development of the instruction set in turn. We could also simply make all opcodes x bytes long, but that goes against the initial reason opcodes were only allowed to be 1 byte long, and would also (likely) break compatibility with existing classfiles as well.

As the old proposal for this JEP suggested, we could do the following:

"Rather than replace the existing 1-byte opcodes in the code attribute for MethodInfo, which would require padding every existing opcode with 0x00 from the front, we'll introduce the concept of Extended Opcodes. Opcode Extension is when a single byte with Code_attribute is extended from the front with an arbitrary number of bytes. This is achieved by storing extension bytes for only specific opcodes, which allows for the typical 1-byte instructions to be stored as they usually are, making this representation compact.

Consider the imaginary opcode 0xCAFEBABE, which is 3 bytes larger than currently possible within any JVM. 0xBE by itself meanwhile is the arraylength opcode. This JEP proposes to store the arbitrarily long opcode 0xCAFEBABE as follows:

0xBE, the least significant byte of the instruction, is stored within the instruction sequence as any other typical opcode. A related extension entry is then stored with 0xCAFEBA stored in correct order as individual byte sequences 0xCA, 0xFE and 0xBA. This entry will also contain the index into code[] that the corresponding byte it is extending is located. At class load time, the JVM will scan for any extended opcodes within the list, search, and then patch the opcode at that index with the bytes stored in that extension entry, which will then be run as normal. Were a corresponding Extended Opcode entry for 0xBE not be present when loaded, it would then be interpreted as the typical arraylength instruction. This allows for class files to be compact, since only extended opcodes have their additional bytes stored, leaving the already existing ones as still representable with a single byte.

To achieve this, we'll introduce the new ExtendedOpcodes attribute for the Code Attribute, leveraging the already present but unused attributes section that Code_attribute already contains.

ExtendedOpcodes has the following structure:

ExtendedOpcodes_attribute { u2 attribute_name_index; u4 attribute_length; u4 extendedOpcodeCount; { u4 counter; u1 count; u1 extensions[count]; } extendedOpcodes[extendedOpcodeCount]; }

Where:

The value of the attribute_name_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Utf8_info structure representing the string "ExtendedOpcodes".

attribute_length contains the length of the attribute, excluding the initial 6 bytes

extendedOpcodeCount is the number of opcodes within code[] that are extended by this new system, subject to the same restraints as code_length in Code_attribute

extendedOpcodes contains a collection of every opcode within the containing Code_attribute that has been extended. Each entry in this collection contains:

counter is a valid index into the code[] collection in the parent Code_attribute to a single byte

count is the number of bytes the target opcode can be extended with. For instance, a count of 1 signifies an addition of up to 0xFF to be placed in front of the corresponding opcode in Code_attributes, a count of 2 would mean up the opcode can be extended to a maximum of 0xFFFF from the front, and so on.

Every byte in extensions[] is, from front to back, the entire value to extend the target opcode from the front. Initializing extensions to {0xCA, 0xFE, 0xBA} for the opcode 0xBE for instance would yield the full instruction 0xCAFEBABE.

Importantly, this attribute is entirely optional, and if the extended opcodes attribute is not present within a code attribute, the opcodes within will simply be assumed to all be the regular 1-byte long opcodes, retaining full compatibility with classfiles that do not contain any extended opcodes. This also allows us to keep the compactness of existing opcodes without having to pad them with empty bytes, which was the primary reason opcodes were originally restricted to only a byte to begin with. Even with any Extended Opcodes within an instruction sequence, only the ones that have been extended have their extra bytes stored, allowing us to ultimately save classfile space. A more compact storage than the one presented in this JEP can hopefully be developed as progress on it continues."

However, after some discussion this was deemed somewhat unwieldy and required too much change to the existing structure of the classloaders and at runtime when the opcodes are being executed. This approach is still listed here as an alternative in case any future discussion could benefit from it

Testing

The are no special platform specific or hardware related aspects for this JEP. The only testing would be verifying whether newer opcodes can be properly extended after implementation without performance degradation.

Risks and Assumptions

This JEP assumes that the JVM's Templating Interpreter and bytecode compilers can handle opcodes of arbitrary length, and that executing opcodes longer than a byte will not have any significant adverse effects on performance, and that the only blocker is compactness within classfiles.

Dependencies

There are currently no JEPs that this one depends on, or depend on this one.