modules/tool-classfile/src/main/java/net/multiphasicapps/classfile/JavaStackShuffleType.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// Multi-Phasic Applications: SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the GNU General Public License v3+, or later.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package net.multiphasicapps.classfile;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
/**
* This represents the type of stack shuffle to perform. Since these
* operations depend on the types on the stack, this is used to contain the
* information to simplify the operations.
*
* @since 2019/03/30
*/
public enum JavaStackShuffleType
{
/** dup. */
DUP("a:aa"),
/** dup_x1. */
DUP_X1("ab:bab"),
/** dup_x2. */
DUP_X2("abc:cabc",
"Ab:bAb"),
/** dup2. */
DUP2("ab:abab",
"A:AA"),
/** dup2_x1. */
DUP2_X1("abc:bcabc",
"aB:BaB"),
/** dup2_x2. */
DUP2_X2("abcd:cdabcd",
"abC:CabC",
"Abc:bcAbc",
"AB:BAB"),
/** pop. */
POP("a:"),
/** pop2. */
POP2("ab:",
"A:"),
/** swap. */
SWAP("ab:ba"),
/* End. */
;
/** Forms of this operation. */
final Function[] _functions;
/**
* Initialize the shuffle form information.
*
* The forms consist of characters for the various items on the stack.
* A lowercase letter represents a narrow type while a capital letter
* represents a wide type. Input and output is separated by a colon. The
* operation is just that whatever is pushed to the stack has the same
* value as the items removed from the stack.
*
* @param __fs The forms.
* @since 2019/04/01
*/
JavaStackShuffleType(String... __fs)
{
int n = __fs.length;
Function[] functions = new Function[n];
for (int i = 0; i < n; i++)
functions[i] = Function.__of(__fs[i]);
this._functions = functions;
}
/**
* Locates the shuffle function that is used to pop from the stack
* accordingly to this stack state.
*
* @param __state The state to load from.
* @return The matching shuffle function.
* @throws InvalidClassFormatException If the shuffle function was not
* found.
* @throws NullPointerException On null arguments.
* @since 2019/04/04
*/
public final JavaStackShuffleType.Function findShuffleFunction(
StackMapTableState __state)
throws InvalidClassFormatException, NullPointerException
{
return JavaStackShuffleType.findShuffleFunction(__state, this);
}
/**
* Locates the shuffle function that is used to pop from the stack
* accordingly to this stack state.
*
* @param __state The state to load from.
* @param __t The type of shuffle to perform.
* @return The matching shuffle function.
* @throws InvalidClassFormatException If the shuffle function was not
* found.
* @throws NullPointerException On null arguments.
* @since 2019/04/04
*/
public static final JavaStackShuffleType.Function findShuffleFunction(
StackMapTableState __state, JavaStackShuffleType __t)
throws InvalidClassFormatException, NullPointerException
{
if (__state == null || __t == null)
throw new NullPointerException("NARG");
// Input stack properties
List<StackMapTableEntry> stack = __state.getStack();
int stacktop = __state.depth();
// Working pop list when match is found
int basetop,
maxpop;
// Search for the matching function to use for this state
for (JavaStackShuffleType.Function tryf : __t._functions)
{
// Clear for run
// Input slots are used
JavaStackShuffleType.Slots sls = tryf.in;
// Too little on the stack to pop everything?
maxpop = sls.max;
basetop = stacktop - maxpop;
if (basetop < 0)
continue;
// Go through slots and see if this is a match or not
int at = basetop;
for (int ldx = 0; at < stacktop; ldx++, at++)
{
StackMapTableEntry i = stack.get(at);
JavaType it = i.type();
// Top-ness and wide-ness does not match
if (it.isTop() != (sls._var[ldx] < 0) ||
it.isWide() != sls._wide[ldx])
break;
}
// If this index was reached then everything was valid
if (at == stacktop)
return tryf;
}
/* {@squirreljme.error JC1q Could not find a match for performing
shuffled stack operations.} */
throw new InvalidClassFormatException("JC1q");
}
/**
* Returns the stack shuffle type for the given operation.
*
* @param __op The operation to get.
* @return The shuffle type for the given operation.
* @throws NoSuchElementException If the operation is not valid.
* @since 2023/07/03
*/
public static JavaStackShuffleType ofOperation(int __op)
throws NoSuchElementException
{
switch (__op)
{
case InstructionIndex.POP:
return JavaStackShuffleType.POP;
case InstructionIndex.POP2:
return JavaStackShuffleType.POP2;
case InstructionIndex.DUP:
return JavaStackShuffleType.DUP;
case InstructionIndex.DUP_X1:
return JavaStackShuffleType.DUP_X1;
case InstructionIndex.DUP_X2:
return JavaStackShuffleType.DUP_X2;
case InstructionIndex.DUP2:
return JavaStackShuffleType.DUP2;
case InstructionIndex.DUP2_X1:
return JavaStackShuffleType.DUP2_X1;
case InstructionIndex.DUP2_X2:
return JavaStackShuffleType.DUP2_X2;
case InstructionIndex.SWAP:
return JavaStackShuffleType.SWAP;
}
/* {@squirreljme.error JC91 Unknown swap type.} */
throw new NoSuchElementException("JC91");
}
/**
* Contains information on how to push or pop operations.
*
* @since 2019/04/01
*/
public static final class Function
{
/** Input slots. */
public final Slots in;
/** Output slots. */
public final Slots out;
/** String reference. */
private Reference<String> _string;
/**
* Initializes the function.
*
* @param __in The input.
* @param __out The output.
* @throws NullPointerException On null arguments.
* @since 2019/04/01
*/
Function(Slots __in, Slots __out)
throws NullPointerException
{
if (__in == null || __out == null)
throw new NullPointerException("NARG");
this.in = __in;
this.out = __out;
}
/**
* Layers the input types to the output.
*
* @param __inTypes The input types.
* @return The layered output types.
* @since 2021/07/04
*/
public JavaType[] layerTypes(JavaType... __inTypes)
{
int outLen = this.out.max;
JavaType[] rv = new JavaType[outLen];
// Debug
Debugging.debugNote("@@layerIn: %s",
Arrays.asList(__inTypes));
// Map types to the output
int at = 0;
for (int i = 0; i < outLen; i++)
{
int outVar = this.out.variable(i);
// If this is a top type, there is no variable mapping so this
// just gets a bit lost here
if (outVar < 0)
continue;
// Otherwise map the slot, note that we need to map a raw
// index to a logical slot for this to work properly
rv[at++] = __inTypes[this.in.logicalSlot(
this.in.findVariableSlot(outVar))];
}
return (at == outLen ? rv : Arrays.copyOf(rv, at));
}
/**
* {@inheritDoc}
* @since 2019/04/04
*/
@Override
public final String toString()
{
Reference<String> ref = this._string;
String rv;
if (ref == null || null == (rv = ref.get()))
this._string = new WeakReference<>((rv =
"[" + this.in + " -> " + this.out + "]"));
return rv;
}
/**
* Returns the function for the given string.
*
* @param __s The string to parse.
* @throws IllegalArgumentException If the function is not valid.
* @throws NullPointerException On null arguments.
* @since 2019/04/01
*/
static Function __of(String __s)
throws IllegalArgumentException, NullPointerException
{
if (__s == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error JC1d Expected colon in function form.} */
int col = __s.indexOf(':');
if (col < 0)
throw new IllegalArgumentException("JC1d");
return new Function(new Slots(__s.substring(0, col)),
new Slots(__s.substring(col + 1)));
}
}
/**
* Represents the slots used for the stack.
*
* @since 2019/04/01
*/
public static final class Slots
{
/** The maximum push/pop count. */
public final int max;
/** Logical maximum push/pop count. */
public final int logicalMax;
/** Mapping to turn indexes into logical slots. */
final byte[] _indexToLogicalSlot;
/** The variable index, negative values mean top types. */
final byte[] _var;
/** Which slots are considered wide or not. */
final boolean[] _wide;
/** String reference. */
private Reference<String> _string;
/**
* Initializes the slots.
*
* @param __s The string source.
* @throws IllegalArgumentException If the slots are not valid.
* @throws NullPointerException On null arguments.
* @since 2019/04/01
*/
Slots(String __s)
throws IllegalArgumentException, NullPointerException
{
if (__s == null)
throw new NullPointerException("NARG");
// Determine the actual popping, with top types and such
int n = __s.length(),
max = 0;
this.logicalMax = n;
for (int i = 0; i < n; i++)
if (Character.isUpperCase(__s.charAt(i)))
max += 2;
else
max += 1;
// Stores top and wide states
byte[] var = new byte[max];
boolean[] wide = new boolean[max];
// Go through again and fill the output
for (int i = 0, o = 0; i < n; i++)
{
char c = __s.charAt(i);
boolean iswide = Character.isUpperCase(c);
// Store information here
var[o] = (byte)(Character.toLowerCase(c) - 'a');
wide[o++] = iswide;
// The tops of wide types are considered narrow but also have
// no variable type
if (iswide)
var[o++] = -1;
}
// Store
this.max = max;
this._var = var;
this._wide = wide;
// Build mapping from indexes to logical slots, so it can be
// determined which index belongs to which slot.
byte[] indexToLogicalSlot = new byte[max];
for (int i = 0, at = 0; i < n; i++)
{
char c = __s.charAt(i);
// Top slots take two
if (c >= 'A' && c <= 'Z')
{
indexToLogicalSlot[at++] = (byte)(c - 'A');
indexToLogicalSlot[at++] = (byte)(c - 'A');
}
else
indexToLogicalSlot[at++] = (byte)(c - 'a');
}
this._indexToLogicalSlot = indexToLogicalSlot;
}
/**
* Finds the slot that the variable is in.
*
* @param __var The variable to search for.
* @return The first slot the variable belongs in.
* @since 2021/07/04
*/
public final int findVariableSlot(int __var)
{
/* {@squirreljme.error JC52 Cannot locate the slot of a wide
value.} */
if (__var < 0)
throw new IllegalArgumentException("JC52");
for (int i = 0, n = this.max; i < n; i++)
if (this.variable(i) == __var)
return i;
/* {@squirreljme.error JC51 Could not find the slot for the given
variable. (The variable)} */
throw new IllegalArgumentException("JC51 " + __var);
}
/**
* Mpas the slots for the given stack map.
*
* @param __stackMap The stack map to use.
* @return The mapped Java types.
* @throws NullPointerException On null arguments.
* @since 2023/08/10
*/
public JavaType[] javaTypes(StackMapTableState __stackMap)
throws NullPointerException
{
if (__stackMap == null)
throw new NullPointerException("NARG");
// The result is always the logical maximum
int logicalMax = this.logicalMax;
JavaType[] result = new JavaType[logicalMax];
// Fill in slots accordingly
for (int i = 0; i < logicalMax; i++)
result[(logicalMax - 1) - i] =
__stackMap.getStackFromLogicalTop(i).type;
return result;
}
/**
* Returns the logical slot for the index.
*
* @param __dx The index.
* @return The logical slot for the index.
* @since 2021/06/20
*/
public final int logicalSlot(int __dx)
{
return this._indexToLogicalSlot[__dx];
}
/**
* Like {@link #logicalVariable(int)} but instead returns the index
* via the logical slot.
*
* @param __dx The index to obtain.
* @return The variable type, this will never return a negative value
* for the top type.
* @see #logicalVariable(int)
* @since 2021/07/04
*/
public int logicalVariable(int __dx)
{
byte[] var = this._var;
int at = 0;
for (int res : var)
{
if (res < 0)
continue;
if (at == __dx)
return res;
at++;
}
/* {@squirreljme.error JC53 Could not find the variable for
the logical slot. (The logical slot)} */
throw new IllegalArgumentException("JC53 " + __dx);
}
/**
* {@inheritDoc}
* @since 2019/04/04
*/
@Override
public final String toString()
{
Reference<String> ref = this._string;
String rv;
if (ref == null || null == (rv = ref.get()))
{
StringBuilder sb = new StringBuilder("[");
// Convert back to close to the original form
byte[] var = this._var;
boolean[] wide = this._wide;
for (int i = 0, n = this.max; i < n; i++)
{
int v = var[i];
boolean w = wide[i];
if (v < 0)
sb.append('+');
else
{
char c = (char)('a' + v);
sb.append((w ? Character.toUpperCase(c) : c));
}
}
// Finish and cache it
sb.append(']');
this._string = new WeakReference<>((rv = sb.toString()));
}
return rv;
}
/**
* Returns the variable to use.
*
* @param __i The index to get.
* @return The variable here, {@code -1} represents a top type.
* @since 2019/04/04
*/
public final int variable(int __i)
{
return this._var[__i];
}
}
}