bluebonnet/JavaBinary/src/JavaCodeWriter.cs
2021-05-26 15:32:56 +03:00

664 lines
21 KiB
C#

using System;
using System.Collections.Generic;
namespace SpaceFlint.JavaBinary
{
public partial class JavaCode
{
public void Write(JavaWriter wtr, JavaAttribute.Code codeAttr)
{
wtr.Where.Push("method body");
EliminateNops();
int codeLength = FillInstructions(wtr);
if (codeLength > 0xFFFE)
throw wtr.Where.Exception("output method is too large");
var labelToOffsetMap = FillJumpTargets(wtr);
codeAttr.code = new byte[codeLength];
FillCodeAttr(codeAttr.code);
codeAttr.maxStack = (ushort) MaxStack;
codeAttr.maxLocals = (ushort) MaxLocals;
WriteExceptionData(codeAttr, labelToOffsetMap);
if (StackMap != null)
{
var attr = StackMap.ToAttribute(wtr, labelToOffsetMap);
if (attr != null)
codeAttr.attributes.Put(attr);
}
if (DebugLocals != null)
{
var attr = new JavaAttribute.LocalVariableTable(DebugLocals, codeLength);
codeAttr.attributes.Put(attr);
}
FillLineNumbers(codeAttr);
wtr.Where.Pop();
}
int FillInstructions(JavaWriter wtr)
{
int codeLength = 0;
foreach (var inst in Instructions)
{
if (inst.Bytes != null)
{
codeLength += inst.Bytes.Length;
continue;
}
byte op = inst.Opcode;
byte kind = instOperandType[op];
bool ok;
wtr.Where.Push($"opcode 0x{op:X2} label 0x{inst.Label:X4}");
if ((kind & 0x80) != 0)
ok = FillInstruction_Const(wtr, inst, op);
else if ((kind & 0x40) != 0)
ok = FillInstruction_Jump(wtr, inst, op, codeLength);
else if ((kind & 0x10) != 0)
ok = FillInstruction_Local(wtr, inst, op);
else
ok = FillInstruction_Special(wtr, inst, op);
if (! ok)
throw wtr.Where.Exception($"unsupported instruction {op:X2} (or operand) at offset {codeLength:X4} (line {inst.Line})");
if (inst.Bytes != null)
codeLength += inst.Bytes.Length;
else
codeLength++;
wtr.Where.Pop();
}
return codeLength;
}
bool FillInstruction_Const(JavaWriter wtr, Instruction inst, byte op)
{
int length = 3;
int count = 0;
if (op >= 0x12 && op <= 0x14)
{
FillInstruction_ConstLoad(wtr, inst);
return true;
}
int constantIndex = -1;
if (op >= 0xB2 && op <= 0xB5)
{
// getstatic/putstatic/getfield/putfield
if (inst.Data is JavaFieldRef vField)
constantIndex = wtr.ConstField(inst.Class, vField);
}
else if (op >= 0xB6 && op <= 0xB8)
{
// invokevirtual/invokespecial/invokestatic
if (inst.Data is JavaMethodRef vMethod)
constantIndex = wtr.ConstMethod(inst.Class, vMethod);
}
else if (op == 0xB9)
{
// invokeinterface
if (inst.Data is JavaMethodRef vMethod)
{
constantIndex = wtr.ConstInterfaceMethod(inst.Class, vMethod);
length = 5;
count = 1; // 'this' argument
int numArgs = vMethod.Parameters.Count;
for (int i = 0; i < numArgs; i++)
count += vMethod.Parameters[i].Type.Category;
}
}
else if (op == 0xBA)
{
// invokedynamic
if (inst.Data is JavaCallSite vCallSite)
{
constantIndex = wtr.ConstInvokeDynamic(vCallSite);
length = 5;
}
}
else if (op >= 0xBB)
{
// new/anewarray/checkcast/instanceof/multianewarray
constantIndex = wtr.ConstClass(inst.Class);
if (op == 0xC5)
{
length++;
count = (int) inst.Data;
}
}
if (constantIndex == -1)
return false;
inst.Bytes = new byte[length];
inst.Bytes[0] = op;
inst.Bytes[1] = (byte) (constantIndex >> 8);
inst.Bytes[2] = (byte) constantIndex;
if (op == 0xB9 || op == 0xC5)
inst.Bytes[3] = (byte) count;
return true;
}
void FillInstruction_ConstLoad(JavaWriter wtr, Instruction inst)
{
int constantIndex = -1;
byte op = 0;
if (inst.Data is long vLong)
{
if (FillInstruction_ConstLoad_Long(inst, vLong))
return;
constantIndex = wtr.ConstLong(vLong);
op = 0x14;
}
else if (inst.Data is double vDouble)
{
constantIndex = wtr.ConstDouble(vDouble);
op = 0x14;
}
else
{
if (inst.Class != null)
{
if (inst.Data is JavaFieldRef vField)
constantIndex = wtr.ConstField(inst.Class, vField);
else if (inst.Data is JavaMethodRef vMethod)
constantIndex = wtr.ConstMethod(inst.Class, vMethod);
else if (inst.Data == null)
constantIndex = wtr.ConstClass(inst.Class);
}
else if (inst.Data is int vInteger)
{
if (FillInstruction_ConstLoad_Integer(inst, vInteger))
return;
constantIndex = wtr.ConstInteger(vInteger);
}
else if (inst.Data is float vFloat)
constantIndex = wtr.ConstFloat(vFloat);
else if (inst.Data is string vString)
constantIndex = wtr.ConstString(vString);
op = (byte) ((constantIndex <= 255) ? 0x12 : 0x13);
inst.Opcode = op;
}
if (constantIndex == -1)
throw wtr.Where.Exception("invalid constant in ldc/ldc_w/ldc2_w instruction");
if (op == 0x12)
{
inst.Bytes = new byte[2];
inst.Bytes[1] = (byte) constantIndex;
}
else
{
inst.Bytes = new byte[3];
inst.Bytes[1] = (byte) (constantIndex >> 8);
inst.Bytes[2] = (byte) constantIndex;
}
inst.Bytes[0] = op;
}
bool FillInstruction_ConstLoad_Integer(Instruction inst, int value)
{
if (value >= -1 && value <= 5)
{
inst.Bytes = new byte[1];
inst.Bytes[0] = (byte) (3 + value); // iconst_m1 .. iconst_5
}
else if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
{
inst.Bytes = new byte[2];
inst.Bytes[0] = 0x10; // bipush
inst.Bytes[1] = (byte) value;
}
else if (value >= short.MinValue && value <= short.MaxValue)
{
inst.Bytes = new byte[3];
inst.Bytes[0] = 0x11; // sipush
inst.Bytes[1] = (byte) (value >> 8);
inst.Bytes[2] = (byte) value;
}
else
return false;
return true;
}
bool FillInstruction_ConstLoad_Long(Instruction inst, long value)
{
if (value >= 0 && value <= 1)
{
inst.Bytes = new byte[1];
inst.Bytes[0] = (byte) (9 + value); // lconst_0, lconst_1
}
else
return false;
return true;
}
bool FillInstruction_Local(JavaWriter wtr, Instruction inst, byte op)
{
if (op == 0x84)
{
FillInstruction_LocalIncrement(wtr, inst, op);
return true;
}
ushort var = (ushort) (int) inst.Data;
if (var <= 3 && op != 0xA9) // if not 'ret' and small var
{
if (op == 0x15) // iload
inst.Opcode = (byte) (0x1A + var);
else if (op == 0x16) // lload
inst.Opcode = (byte) (0x1E + var);
else if (op == 0x17) // fload
inst.Opcode = (byte) (0x22 + var);
else if (op == 0x18) // dload
inst.Opcode = (byte) (0x26 + var);
else if (op == 0x19) // aload
inst.Opcode = (byte) (0x2A + var);
else if (op == 0x36) // istore
inst.Opcode = (byte) (0x3B + var);
else if (op == 0x37) // lstore
inst.Opcode = (byte) (0x3F + var);
else if (op == 0x38) // fstore
inst.Opcode = (byte) (0x43 + var);
else if (op == 0x39) // dstore
inst.Opcode = (byte) (0x47 + var);
else if (op == 0x3A) // astore
inst.Opcode = (byte) (0x4B + var);
else
return false;
}
else if (var <= 255)
{
inst.Bytes = new byte[2];
inst.Bytes[0] = op;
inst.Bytes[1] = (byte) var;
}
else
{
inst.Bytes = new byte[4];
inst.Bytes[0] = 0xC4; // wide
inst.Bytes[1] = op;
inst.Bytes[2] = (byte) (var >> 8);
inst.Bytes[3] = (byte) var;
}
return true;
}
void FillInstruction_LocalIncrement(JavaWriter wtr, Instruction inst, byte op)
{
ushort var = (ushort) ((uint) inst.Data & 0xFFFF);
ushort inc = (ushort) ((uint) inst.Data >> 16);
if (var <= 255 && inc <= 255)
{
inst.Bytes = new byte[3];
inst.Bytes[0] = op;
inst.Bytes[1] = (byte) var;
inst.Bytes[2] = (byte) inc;
}
else
{
inst.Bytes = new byte[5];
inst.Bytes[0] = op;
inst.Bytes[1] = (byte) (var >> 8);
inst.Bytes[2] = (byte) var;
inst.Bytes[3] = (byte) (inc >> 8);
inst.Bytes[4] = (byte) inc;
}
}
bool FillInstruction_Special(JavaWriter wtr, Instruction inst, byte op)
{
if (op == 0xBC)
{
inst.Bytes = new byte[2];
inst.Bytes[0] = op;
inst.Bytes[1] = (byte) inst.Data;
}
return true;
}
bool FillInstruction_Jump(JavaWriter wtr, Instruction inst, byte op, int offset)
{
if (inst.Data is ushort && op < 0xC8) // insts with 2-byte offsets
{
inst.Bytes = new byte[3];
inst.Bytes[0] = op;
}
else if (inst.Data is ushort && op == 0xC8)
{
inst.Bytes = new byte[5];
inst.Bytes[0] = op;
}
else if (inst.Data is int[] words && op >= 0xAA && op <= 0xAB)
{
int n = words.Length;
if ( n > 16384
|| (op == 0xAA && (n <= 3 || n != 3 + words[2] - words[1] + 1))
|| (op == 0xAB && (n <= 2 || n != 2 + words[1] * 2)))
{
throw wtr.Where.Exception("invalid data in switch instruction");
}
inst.Bytes = new byte[4 - (offset & 3) + n * 4];
inst.Bytes[0] = op;
}
else
return false;
return true;
}
Dictionary<ushort, ushort> FillJumpTargets(JavaWriter wtr)
{
var jumpTuples = new List<ValueTuple<Instruction, ushort>>();
var labelToOffsetMap = new Dictionary<ushort, ushort>();
ushort offset = 0;
foreach (var inst in Instructions)
{
byte kind = instOperandType[inst.Opcode];
if ((kind & 0x40) == 0x40)
{
if (inst.Data is int intOffset)
{
// int data is a jump offset that can be calculated immediately
// note that this prevents nop elimination; see EliminateNops
intOffset -= offset;
inst.Bytes[1] = (byte) (offset >> 8);
inst.Bytes[2] = (byte) offset;
}
else if (inst.Data is ushort || inst.Data is int[])
{
// ushort data is a label, should be converted to a jump offset
jumpTuples.Add(new ValueTuple<Instruction, ushort>(inst, offset));
}
}
if (! labelToOffsetMap.ContainsKey(inst.Label))
labelToOffsetMap[inst.Label] = offset;
if (inst.Bytes == null)
offset++;
else
offset += (ushort) inst.Bytes.Length;
}
foreach (var jumpTuple in jumpTuples)
{
var inst = jumpTuple.Item1;
bool ok;
if (inst.Data is int[])
ok = FillJumpTargets_Switch(inst, jumpTuple.Item2, labelToOffsetMap);
else if (labelToOffsetMap.TryGetValue((ushort) inst.Data, out offset))
{
var offset32 = (int) offset - (int) jumpTuple.Item2;
if (inst.Bytes[0] == 0xC8) // goto_w
{
inst.Bytes[1] = (byte) (offset32 >> 24);
inst.Bytes[2] = (byte) (offset32 >> 16);
inst.Bytes[3] = (byte) (offset32 >> 8);
inst.Bytes[4] = (byte) offset32;
}
else
{
if (offset32 < Int16.MinValue || offset32 > Int16.MaxValue)
throw wtr.Where.Exception($"jump offset too far (in label {inst.Label:X4}, source line {inst.Line})");
offset -= jumpTuple.Item2;
inst.Bytes[1] = (byte) (offset >> 8);
inst.Bytes[2] = (byte) offset;
}
ok = true;
}
else
ok = false;
if (! ok)
throw wtr.Where.Exception($"jump to undefined label (in label {inst.Label:X4}, source line {inst.Line})");
}
return labelToOffsetMap;
}
bool FillJumpTargets_Switch(Instruction inst, int instOffset,
Dictionary<ushort, ushort> labelToOffsetMap)
{
if (inst.Opcode != 0xAA)
return false;
var words = (int[]) inst.Data;
if (! labelToOffsetMap.TryGetValue((ushort) words[0], out var jumpOffset))
return false;
int o = 4 - (instOffset & 3);
WriteInt32(inst.Bytes, o, jumpOffset - instOffset);
WriteInt32(inst.Bytes, o += 4, words[1]);
WriteInt32(inst.Bytes, o += 4, words[2]);
int n = words.Length;
for (int i = 3; i < n; i++)
{
if (! labelToOffsetMap.TryGetValue((ushort) words[i], out jumpOffset))
return false;
WriteInt32(inst.Bytes, o += 4, jumpOffset - instOffset);
}
return true;
void WriteInt32(byte[] bytes, int ofs, int val)
{
bytes[ofs + 0] = (byte) (val >> 24);
bytes[ofs + 1] = (byte) (val >> 16);
bytes[ofs + 2] = (byte) (val >> 8);
bytes[ofs + 3] = (byte) val;
}
}
void FillCodeAttr(byte[] codeText)
{
int offset = 0;
foreach (var inst in Instructions)
{
if (inst.Bytes == null)
{
codeText[offset++] = inst.Opcode;
}
else
{
for (int i = 0; i < inst.Bytes.Length; i++)
codeText[offset++] = inst.Bytes[i];
}
}
}
void FillLineNumbers(JavaAttribute.Code codeAttr)
{
var lines = new List<JavaAttribute.LineNumberTable.Item>();
JavaAttribute.LineNumberTable.Item item;
item.lineNumber = 0xFFFF;
ushort offset = 0;
foreach (var inst in Instructions)
{
if (inst.Line != 0 && inst.Line != item.lineNumber)
{
item.offset = offset;
item.lineNumber = inst.Line;
lines.Add(item);
}
offset += (ushort) ((inst.Bytes == null) ? 1 : inst.Bytes.Length);
}
if (lines.Count != 0)
{
codeAttr.attributes.Put(
new JavaAttribute.LineNumberTable(lines.ToArray()));
}
}
void WriteExceptionData(JavaAttribute.Code codeAttr, Dictionary<ushort, ushort> labelToOffsetMap)
{
int n;
if ((Exceptions != null) && (n = Exceptions.Count) > 0)
{
codeAttr.exceptions = new JavaAttribute.Code.Exception[n];
for (int i = 0; i < n; i++)
{
JavaAttribute.Code.Exception exceptionItem = Exceptions[i];
if (labelToOffsetMap.TryGetValue(exceptionItem.start, out var startOffset))
exceptionItem.start = startOffset;
if (labelToOffsetMap.TryGetValue(exceptionItem.endPlus1, out var endPlus1Offset))
exceptionItem.endPlus1 = endPlus1Offset;
if (labelToOffsetMap.TryGetValue(exceptionItem.handler, out var handlerOffset))
exceptionItem.handler = handlerOffset;
codeAttr.exceptions[i] = exceptionItem;
}
}
}
public static int LengthOfLocalVariableInstruction(int index)
{
// based on the given index value, determine the minimum length
// needed for an xload/xstore instruction (e.g. astore).
// one byte: index <= 3 translates to xstore_N
// two bytes: index <= 255 translates to standard xstore
// four bytes: with a wide prefix and two bytes for index
return (index <= 3) ? 1 : ((index <= 255) ? 2 : 4);
}
void EliminateNops()
{
// remove any nop instructions that are not a branch target,
// and only if there are no exception tables. note that
// removing nops that are a branch target, or nops in a method
// with exception tables, would require updating the stack map,
// branch instructions and exception tables.
if (Exceptions != null && Exceptions.Count != 0)
return;
var nops = new List<int>();
int n = Instructions.Count;
for (int i = 0; i < n; i++)
{
byte op = Instructions[i].Opcode;
if (op == 0x00 /* nop */)
{
// collect this nop only if it is not a branch target
if (! StackMap.HasBranchFrame(Instructions[i].Label))
{
nops.Add(i);
}
}
else if ((instOperandType[op] & 0x40) == 0x40) // jump inst
{
if (Instructions[i].Data is int)
{
// if jump instruction has an explicit byte offset,
// we can't do nop elimination; see FillJumpTargets
return;
}
}
}
for (int i = nops.Count; i-- > 0; )
Instructions.RemoveAt(nops[i]);
}
}
}