/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.max.vm.ext.maxri;
import static com.sun.max.platform.Platform.*;
import static com.sun.max.vm.MaxineVM.*;
import java.lang.reflect.*;
import java.util.*;
import com.oracle.max.vm.ext.maxri.Package.MaxRiObjectMapContributor;
import com.sun.cri.ci.*;
import com.sun.max.annotate.*;
import com.sun.max.vm.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.runtime.*;
/**
* Utility for encoding/decoding {@link CiValue}s to/from encoding/decoding streams.
*/
public class ValueCodec {
final static class UnsignedBitField {
/**
* Number of bits in value.
*/
final int width;
/**
* Bit position of lowest bit in field.
*/
final int shift;
/**
* Mask applied to (unshifted) value to obtain field.
*/
final int mask;
int max() {
return mask;
}
UnsignedBitField(int width, int shift) {
assert width < 32;
this.width = width;
this.shift = shift;
this.mask = (1 << width) - 1;
}
/**
* Extracts the value of this bit field from {@code word}.
*/
int get(int word) {
return (word >>> shift) & mask;
}
/**
* Sets the value of this bit field in {@code word}.
*
* @return the value of {@code word} modified such that {@code this.get(word) == value}
*/
int set(int word, int value) {
assert (value & mask) == value : "value is out of range";
word &= ~(mask << shift); // clear current value
word |= value << shift; // set new value
assert get(word) == value;
return word;
}
}
/**
* Bit field in first byte of an encoded {@link CiValue} specifying the type of the value.
*/
final static UnsignedBitField TYPE = new UnsignedBitField(2, 6);
/**
* Value in {@link #TYPE} bit field denoting a {@link CiRegisterValue}.
*/
final static int TYPE_REGISTER = 0;
/**
* Value in {@link #TYPE} bit field denoting a {@link CiStackSlot}.
*/
final static int TYPE_STACK = 1;
/**
* Value in {@link #TYPE} bit field denoting an object {@link CiConstant}.
*/
final static int TYPE_OBJECT_CONSTANT = 2;
/**
* Value in {@link #TYPE} bit field denoting a non-object {@link CiConstant} or {@link CiValue#IllegalValue}.
*/
final static int TYPE_NONOBJECT_CONSTANT = 3;
/**
* Bit field in first byte of an encoded {@link CiStackSlot} specifying the stack slot index and frame category (current frame of caller frame).
*/
final static UnsignedBitField STACK = new UnsignedBitField(6, 0);
/**
* Value in {@link #STACK} bit field specifying that the 4 following bytes encode the index of a slot in the current frame.
*/
final static int STACK_5_CURRENT_FRAME = STACK.max();
/**
* Value in {@link #STACK} bit field specifying that the 4 following bytes encode the index of a slot in the caller frame
* (e.g. a stack slot for an incoming parameter).
*/
final static int STACK_5_CALLER_FRAME = STACK_5_CURRENT_FRAME - 1;
/**
* Maximum value in the {@link #STACK} bit field encoding a slot index and frame category.
*/
final static int STACK_1_MAX = STACK_5_CALLER_FRAME - 1;
/**
* Maximum number of caller frame slots that can be encoded in one byte.
*/
final static int STACK_1_CALLER_FRAME_MAX = 8;
/**
* Maximum number of current frame slots that can be encoded in one byte.
*/
final static int STACK_1_CURRENT_FRAME_MAX = STACK_1_MAX - STACK_1_CALLER_FRAME_MAX - 1;
/**
* Bit field in first byte of an encoded {@link CiRegisterValue} specifying the {@link CiRegister#number register number}.
*/
final static UnsignedBitField REGISTER = new UnsignedBitField(6, 0);
/**
* Bit field in first byte of an encoded object {@link CiConstant} specifying the value.
*/
final static UnsignedBitField OBJECT_CONSTANT = new UnsignedBitField(6, 0);
/**
* Bit field in first byte of an encoded non-object {@link CiConstant} specifying the value.
*/
final static UnsignedBitField NONOBJECT_CONSTANT = new UnsignedBitField(6, 0);
/**
* Maximum value in the {@link #NONOBJECT_CONSTANT} bit field encoding an index in {@link #nonObjectConstantsByID}.
* The complete range of indexes encodable in {@link #NONOBJECT_CONSTANT} is {@code [0 .. NONOBJECT_CONSTANT_SHARED_INDEX_MAX]}.
* The range of values between {@code [NONOBJECT_CONSTANT_SHARED_INDEX_MAX + 1 .. NONOBJECT_CONSTANT.max]} encode the
* ordinal of the value's kind and the following bytes encode the value itself.
*/
final static int NONOBJECT_CONSTANT_SHARED_INDEX_MAX = OBJECT_CONSTANT.max() - CiKind.VALUES.length;
/**
* Constant pool of object {@link CiConstant} values.
* This pool is re-initialized lazily at runtime to account for {@link System#identityHashCode(Object)}
* giving different values at boot image time.
*
* @see MaxRiObjectMapContributor
*/
static final IdentityHashMap<Object, Integer> objectConstants = new IdentityHashMap<Object, Integer>();
/**
* This is used to decode an index back to an object constant in {@link #objectConstants}.
*/
static final LinearIDMap<CiConstant> objectConstantsByID = new LinearIDMap<CiConstant>(300);
/**
* Constant pool of shared non-object {@link CiConstant} values.
*
* The special entry with a key of {@code null} denotes {@link CiValue#IllegalValue}.
*/
static final HashMap<CiConstant, Integer> nonObjectConstants = new HashMap<CiConstant, Integer>();
/**
* This is used to decode an index back to a non-object constant in {@link #nonObjectConstants}.
*/
static final LinearIDMap<CiConstant> nonObjectConstantsByID = new LinearIDMap<CiConstant>(20);
/**
* Reserved non-object constant index denoting {@link CiValue#IllegalValue}.
*/
final static int NONOBJECT_CONSTANT_INDEX_ILLEGAL_VALUE = 0;
/**
* Reserved non-object constant index denoting that the following value is an encoded {@code long} stack slot or register.
*/
final static int NONOBJECT_CONSTANT_INDEX_LONG_STACKSLOT_OR_REGISTER = 1;
/**
* Reserved non-object constant index denoting that the following value is an encoded {@code double} stack slot or register.
*/
final static int NONOBJECT_CONSTANT_INDEX_DOUBLE_STACKSLOT_OR_REGISTER = 2;
/**
* Reserved non-object constant index denoting that following is an encoded {@link CiMonitorValue}.
*/
final static int NONOBJECT_CONSTANT_INDEX_MONITOR_VALUE = 3;
static {
// Reserve index 0 for CiValue.IllegalValue
nonObjectConstants.put(CiConstant.forObject(new Object()), NONOBJECT_CONSTANT_INDEX_ILLEGAL_VALUE);
// Reserve index 1 to denote the previous register or stack slot is a long
nonObjectConstants.put(CiConstant.forObject(new Object()), NONOBJECT_CONSTANT_INDEX_LONG_STACKSLOT_OR_REGISTER);
// Reserve index 2 to denote the previous register or stack slot is a double
nonObjectConstants.put(CiConstant.forObject(new Object()), NONOBJECT_CONSTANT_INDEX_DOUBLE_STACKSLOT_OR_REGISTER);
// Reserve index 3 to denote an encoded monitor
nonObjectConstants.put(CiConstant.forObject(new Object()), NONOBJECT_CONSTANT_INDEX_MONITOR_VALUE);
for (Field field : CiConstant.class.getFields()) {
if (field.getType() == CiConstant.class) {
try {
field.setAccessible(true);
CiConstant c = (CiConstant) field.get(null);
if (c.kind.isObject()) {
Object obj = c.asObject();
if (!objectConstants.containsKey(obj)) {
int index = objectConstants.size();
objectConstants.put(obj, index);
objectConstantsByID.set(index, c);
}
} else {
if (!nonObjectConstants.containsKey(c)) {
int index = nonObjectConstants.size();
nonObjectConstants.put(c, index);
nonObjectConstantsByID.set(index, c);
}
}
} catch (Exception e) {
throw FatalError.unexpected("Error reading value of " + field, e);
}
}
}
}
static long valuesEncoded;
static long valuesEncodedSize;
/**
* Encodes a {@link CiValue} to a data output stream.
*/
static void writeValue(EncodingStream out, CiValue value) {
int pos = out.pos;
if (value.isIllegal()) {
out.write(TYPE.set(NONOBJECT_CONSTANT_INDEX_ILLEGAL_VALUE, TYPE_NONOBJECT_CONSTANT));
} else if (value.isRegister()) {
writeLongOrDoublePrefix(out, value);
int b = TYPE.set(0, TYPE_REGISTER);
b = REGISTER.set(b, value.asRegister().number);
out.write(b);
} else if (value.isStackSlot()) {
writeLongOrDoublePrefix(out, value);
CiStackSlot slot = (CiStackSlot) value;
int index = slot.index();
if (slot.inCallerFrame()) {
if (index <= STACK_1_CALLER_FRAME_MAX) {
out.write(STACK.set(TYPE.set(0, TYPE_STACK), index + STACK_1_CURRENT_FRAME_MAX + 1));
} else {
out.write(STACK.set(TYPE.set(0, TYPE_STACK), STACK_5_CALLER_FRAME));
out.writeInt(index);
}
} else {
if (index <= STACK_1_CURRENT_FRAME_MAX) {
out.write(STACK.set(TYPE.set(0, TYPE_STACK), index));
} else {
out.write(STACK.set(TYPE.set(0, TYPE_STACK), STACK_5_CURRENT_FRAME));
out.writeInt(index);
}
}
} else if (value.isMonitor()) {
CiMonitorValue monitor = (CiMonitorValue) value;
out.write(TYPE.set(NONOBJECT_CONSTANT_INDEX_MONITOR_VALUE, TYPE_NONOBJECT_CONSTANT));
writeValue(out, monitor.owner);
writeValue(out, monitor.lockData);
writeValue(out, CiConstant.forBoolean(monitor.eliminated));
} else {
assert value.isConstant() : "cannot encode " + value;
CiConstant c = (CiConstant) value;
if (c.kind.isObject()) {
int index;
Object obj = c.asObject();
synchronized (objectConstants) {
if (!isHosted() && objectConstants.isEmpty()) {
// re-initialize the map
for (int id = 0; id <= objectConstantsByID.maxID(); id++) {
CiConstant e = objectConstantsByID.get(id);
if (e != null) {
objectConstants.put(e.asObject(), id);
}
}
}
Integer key = objectConstants.get(obj);
if (key == null) {
index = objectConstants.size();
objectConstants.put(obj, index);
objectConstantsByID.set(index, c);
} else {
index = key;
}
}
if (index < OBJECT_CONSTANT.max()) {
out.write(OBJECT_CONSTANT.set(TYPE.set(0, TYPE_OBJECT_CONSTANT), index));
} else {
out.write(OBJECT_CONSTANT.set(TYPE.set(0, TYPE_OBJECT_CONSTANT), OBJECT_CONSTANT.max()));
out.encodeUInt(index);
}
} else {
Integer index = nonObjectConstants.get(c);
if (index != null) {
assert index > 0 && index <= NONOBJECT_CONSTANT_SHARED_INDEX_MAX;
out.write(NONOBJECT_CONSTANT.set(TYPE.set(0, TYPE_NONOBJECT_CONSTANT), index));
} else {
out.write(NONOBJECT_CONSTANT.set(TYPE.set(0, TYPE_NONOBJECT_CONSTANT), c.kind.ordinal() + NONOBJECT_CONSTANT_SHARED_INDEX_MAX + 1));
// Checkstyle: stop
switch (c.kind) {
case Byte : out.writeByte(c.asInt()); break;
case Boolean : out.writeBoolean(c.asBoolean()); break;
case Char : out.writeChar(c.asInt()); break;
case Short : out.writeShort(c.asInt()); break;
case Float : out.writeFloat(c.asFloat()); break;
case Long : out.writeLong(c.asLong()); break;
case Double : out.writeDouble(c.asDouble()); break;
case Jsr : out.writeInt(c.asInt()); break;
case Int: {
int b0 = c.asInt();
if (b0 >= 0 && b0 < 0xff) {
out.write(b0);
} else {
out.write(0xff);
out.writeInt(c.asInt());
}
break;
}
default: throw FatalError.unexpected("Unexpected kind: " + c.kind);
}
// Checkstyle: resume
}
}
}
valuesEncoded++;
valuesEncodedSize += out.pos - pos;
}
/**
* Writes the prefix denoting that a following register or stack slot value in the stream is of kind long or double.
*/
private static void writeLongOrDoublePrefix(EncodingStream out, CiValue value) {
if (value.kind.isLong()) {
out.write(TYPE.set(NONOBJECT_CONSTANT_INDEX_LONG_STACKSLOT_OR_REGISTER, TYPE_NONOBJECT_CONSTANT));
} else if (value.kind.isDouble()) {
out.write(TYPE.set(NONOBJECT_CONSTANT_INDEX_DOUBLE_STACKSLOT_OR_REGISTER, TYPE_NONOBJECT_CONSTANT));
}
}
/**
* Decodes a {@link CiValue} from a data input stream.
*/
static CiValue readValue(DecodingStream in, CiBitMap regRefMap, CiBitMap frameRefMap) {
int b = in.read();
assert b >= 0;
int type = TYPE.get(b);
if (type == TYPE_STACK) {
int index = STACK.get(b);
if (index <= STACK_1_CURRENT_FRAME_MAX) {
if (frameRefMap != null && frameRefMap.get(index)) {
return CiStackSlot.get(CiKind.Object, index, false);
}
return CiStackSlot.get(WordUtil.archKind(), index, false);
} else if (index <= STACK_1_MAX) {
return CiStackSlot.get(WordUtil.archKind(), index - STACK_1_CURRENT_FRAME_MAX - 1, true);
} else if (index == STACK_5_CALLER_FRAME) {
index = in.readInt();
return CiStackSlot.get(WordUtil.archKind(), index, true);
} else {
assert index == STACK_5_CURRENT_FRAME;
index = in.readInt();
if (frameRefMap != null && frameRefMap.get(index)) {
return CiStackSlot.get(CiKind.Object, index, false);
}
return CiStackSlot.get(WordUtil.archKind(), index, false);
}
} else if (type == TYPE_REGISTER) {
int num = REGISTER.get(b);
if (regRefMap != null && num < regRefMap.size() && regRefMap.get(num)) {
return target().arch.registers[num].asValue(CiKind.Object);
}
return target().arch.registers[num].asValue(WordUtil.archKind());
} else if (type == TYPE_OBJECT_CONSTANT) {
int index = OBJECT_CONSTANT.get(b);
if (index < OBJECT_CONSTANT.max()) {
return objectConstantsByID.get(index);
}
index = in.decodeUInt();
return objectConstantsByID.get(index);
} else {
assert type == TYPE_NONOBJECT_CONSTANT;
int index = NONOBJECT_CONSTANT.get(b);
if (index == NONOBJECT_CONSTANT_INDEX_ILLEGAL_VALUE) {
return CiValue.IllegalValue;
} else if (index == NONOBJECT_CONSTANT_INDEX_MONITOR_VALUE) {
CiValue owner = readValue(in, regRefMap, frameRefMap);
CiValue lockData = readValue(in, regRefMap, frameRefMap);
CiConstant eliminated = (CiConstant) readValue(in, regRefMap, frameRefMap);
if (lockData.isIllegal()) {
lockData = null;
}
return new CiMonitorValue(owner, lockData, eliminated.asBoolean());
} else if (index == NONOBJECT_CONSTANT_INDEX_LONG_STACKSLOT_OR_REGISTER) {
CiValue value = readValue(in, regRefMap, frameRefMap);
if (value.isStackSlot()) {
CiStackSlot slot = (CiStackSlot) value;
return CiStackSlot.get(CiKind.Long, slot.index(), slot.inCallerFrame());
} else {
assert value.isRegister();
CiRegisterValue reg = (CiRegisterValue) value;
return reg.reg.asValue(CiKind.Long);
}
} else if (index == NONOBJECT_CONSTANT_INDEX_DOUBLE_STACKSLOT_OR_REGISTER) {
CiValue value = readValue(in, regRefMap, frameRefMap);
if (value.isStackSlot()) {
CiStackSlot slot = (CiStackSlot) value;
return CiStackSlot.get(CiKind.Double, slot.index(), slot.inCallerFrame());
} else {
assert value.isRegister();
CiRegisterValue reg = (CiRegisterValue) value;
return reg.reg.asValue(CiKind.Double);
}
} else if (index <= NONOBJECT_CONSTANT_SHARED_INDEX_MAX) {
return nonObjectConstantsByID.get(index);
}
int kindOrdinal = index - NONOBJECT_CONSTANT_SHARED_INDEX_MAX - 1;
CiKind kind = CiKind.VALUES[kindOrdinal];
// Checkstyle: stop
switch (kind) {
case Byte : return CiConstant.forByte(in.readByte());
case Boolean : return CiConstant.forBoolean(in.readBoolean());
case Char : return CiConstant.forChar(in.readChar());
case Short : return CiConstant.forShort(in.readShort());
case Float : return CiConstant.forFloat(in.readFloat());
case Long : return CiConstant.forLong(in.readLong());
case Double : return CiConstant.forDouble(in.readDouble());
case Jsr : return CiConstant.forJsr(in.readInt());
case Int: {
int b0 = in.readByte() & 0xFF;
if (b0 == 0xFF) {
return CiConstant.forInt(in.readInt());
} else {
return CiConstant.forInt(b0);
}
}
default: throw FatalError.unexpected("Unexpected kind: " + kind);
}
// Checkstyle: resume
}
}
/**
* Tests codec by encoding and decoding a given value.
*
* @param value the value to pass through the codec
*/
@HOSTED_ONLY
static CiValue testCodec(CiValue value) {
EncodingStream es = new EncodingStream(1024);
writeValue(es, value);
return readValue(new DecodingStream(es.toByteArray()), null, null);
}
}