GP-1256: Permit reading/writing of contextreg bit fields in traces.

This commit is contained in:
Dan 2021-09-07 11:02:30 -04:00
parent c1726c6e4f
commit ca3228b063
10 changed files with 661 additions and 473 deletions

View file

@ -26,7 +26,8 @@ public interface TraceDefinedDataRegisterView
extends TraceDefinedDataView, TraceBaseDefinedRegisterView<TraceData> { extends TraceDefinedDataView, TraceBaseDefinedRegisterView<TraceData> {
default TraceData create(Range<Long> lifespan, Register register, DataType dataType) default TraceData create(Range<Long> lifespan, Register register, DataType dataType)
throws CodeUnitInsertionException { throws CodeUnitInsertionException {
TraceRegisterUtils.requireByteBound(register);
return create(lifespan, register.getAddress(), dataType, return create(lifespan, register.getAddress(), dataType,
TraceRegisterUtils.byteLengthOf(register)); register.getNumBytes());
} }
} }

View file

@ -54,13 +54,50 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return getStates(snap, TraceRegisterUtils.rangeForRegister(register)); return getStates(snap, TraceRegisterUtils.rangeForRegister(register));
} }
/**
* Set the value of a register at the given snap
*
* <p>
* <b>IMPORTANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given value specifies, e.g.,
* only a single bit, then the entire byte will become marked {@link TraceMemoryState#KNOWN},
* even though the remaining 7 bits could technically be unknown.
*
* @param snap the snap
* @param value the register value
* @return the number of bytes written
*/
default int setValue(long snap, RegisterValue value) { default int setValue(long snap, RegisterValue value) {
ByteBuffer buf = TraceRegisterUtils.bufferForValue(value); if (!value.hasAnyValue()) {
return putBytes(snap, value.getRegister().getAddress(), buf); return 0;
}
Register reg = value.getRegister();
if (!value.hasValue() || !TraceRegisterUtils.isByteBound(reg)) {
RegisterValue old = getValue(snap, reg.getBaseRegister());
// Do not use .getRegisterValue, as that will zero unmasked bits
// Instead, we'll pass the original register to bufferForValue
value = old.combineValues(value);
}
ByteBuffer buf = TraceRegisterUtils.bufferForValue(reg, value);
return putBytes(snap, reg.getAddress(), buf);
} }
/**
* Write bytes at the given snap and register address
*
* <p>
* Note that bit-masked registers are not properly heeded. If the caller wishes to preserve
* non-masked bits, it must first retrieve the current value and combine it with the desired
* value. The caller must also account for any bit shift in the passed buffer. Alternatively,
* consider {@link #setValue(long, RegisterValue)}.
*
* @param snap the snap
* @param register the register to modify
* @param buf the buffer of bytes to write
* @return the number of bytes written
*/
default int putBytes(long snap, Register register, ByteBuffer buf) { default int putBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = TraceRegisterUtils.byteLengthOf(register); int byteLength = register.getNumBytes();
int limit = buf.limit(); int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength)); buf.limit(Math.min(limit, buf.position() + byteLength));
int result = putBytes(snap, register.getAddress(), buf); int result = putBytes(snap, register.getAddress(), buf);
@ -79,7 +116,7 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
} }
default int getBytes(long snap, Register register, ByteBuffer buf) { default int getBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = TraceRegisterUtils.byteLengthOf(register); int byteLength = register.getNumBytes();
int limit = buf.limit(); int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength)); buf.limit(Math.min(limit, buf.position() + byteLength));
int result = getBytes(snap, register.getAddress(), buf); int result = getBytes(snap, register.getAddress(), buf);
@ -87,8 +124,21 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return result; return result;
} }
/**
* Remove a value from the given time and register
*
* <p>
* <b>IMPORANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given register specifies,
* e.g., only a single bit, then the entire byte will become marked
* {@link TraceMemoryState#UNKNOWN}, even though the remaining 7 bits could technically be
* known.
*
* @param snap the snap
* @param register the register
*/
default void removeValue(long snap, Register register) { default void removeValue(long snap, Register register) {
int byteLength = TraceRegisterUtils.byteLengthOf(register); int byteLength = register.getNumBytes();
removeBytes(snap, register.getAddress(), byteLength); removeBytes(snap, register.getAddress(), byteLength);
} }

View file

@ -34,16 +34,7 @@ public enum TraceRegisterUtils {
public static AddressRange rangeForRegister(Register register) { public static AddressRange rangeForRegister(Register register) {
Address address = register.getAddress(); Address address = register.getAddress();
return new AddressRangeImpl(address, address.add(register.getMinimumByteSize() - 1)); return new AddressRangeImpl(address, address.add(register.getNumBytes() - 1));
}
public static int byteLengthOf(Register register) {
int bitLength = register.getBitLength();
if ((bitLength & 7) != 0) {
throw new IllegalArgumentException(
"Cannot work with sub-byte boundaries. Consider using the base register.");
}
return bitLength >> 3;
} }
public static byte[] padOrTruncate(byte[] arr, int length) { public static byte[] padOrTruncate(byte[] arr, int length) {
@ -58,33 +49,22 @@ public enum TraceRegisterUtils {
return Arrays.copyOfRange(arr, arr.length - length, arr.length); return Arrays.copyOfRange(arr, arr.length - length, arr.length);
} }
public static ByteBuffer bufferForValue(RegisterValue value) { public static ByteBuffer bufferForValue(Register reg, RegisterValue value) {
byte[] arr = value.getSignedValue().toByteArray(); byte[] bytes = value.toBytes().clone();
int byteLength = byteLengthOf(value.getRegister()); int start = bytes.length / 2;
arr = padOrTruncate(arr, byteLength); // NB: I guess contextreg is always big?
if (!value.getRegister().isBigEndian()) { if (!reg.isBigEndian() && !reg.isProcessorContext()) {
ArrayUtils.reverse(arr); ArrayUtils.reverse(bytes, start, bytes.length);
} }
return ByteBuffer.wrap(arr); int offset = TraceRegisterUtils.computeMaskOffset(reg);
} return ByteBuffer.wrap(bytes, start + offset, reg.getNumBytes());
public static int computeMaskOffset(byte[] arr) {
for (int i = 0; i < arr.length; i++) {
switch (arr[i]) {
case -1:
return i;
case 0:
continue;
default:
throw new IllegalArgumentException(
"Can only handle sub-registers on byte boundaries");
}
}
throw new IllegalArgumentException("No value");
} }
public static int computeMaskOffset(Register reg) { public static int computeMaskOffset(Register reg) {
return computeMaskOffset(reg.getBaseMask()); if (reg.isBaseRegister()) {
return 0;
}
return reg.getOffset() - reg.getBaseRegister().getOffset();
} }
public static int computeMaskOffset(RegisterValue value) { public static int computeMaskOffset(RegisterValue value) {
@ -162,21 +142,38 @@ public enum TraceRegisterUtils {
return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv); return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv);
} }
public static RegisterValue getRegisterValue(Register register, public static RegisterValue getRegisterValue(Register reg,
BiConsumer<Address, ByteBuffer> readAction) { BiConsumer<Address, ByteBuffer> readAction) {
int byteLength = TraceRegisterUtils.byteLengthOf(register); /*
byte[] mask = register.getBaseMask(); * The byte array for reg values spans the whole base register, but we'd like to avoid
* over-reading, so we'll zero in on the bytes actually included in the mask. We'll then
* have to handle endianness and such. The regval instance should then apply the actual mask
* for the sub-register, if applicable.
*/
int byteLength = reg.getNumBytes();
byte[] mask = reg.getBaseMask();
ByteBuffer buf = ByteBuffer.allocate(mask.length * 2); ByteBuffer buf = ByteBuffer.allocate(mask.length * 2);
buf.put(mask); buf.put(mask);
int maskOffset = TraceRegisterUtils.computeMaskOffset(mask); int maskOffset = TraceRegisterUtils.computeMaskOffset(reg);
int startVal = buf.position() + maskOffset; int startVal = buf.position() + maskOffset;
buf.position(startVal); buf.position(startVal);
buf.limit(buf.position() + byteLength); buf.limit(buf.position() + byteLength);
readAction.accept(register.getAddress(), buf); readAction.accept(reg.getAddress(), buf);
byte[] arr = buf.array(); byte[] arr = buf.array();
if (!register.isBigEndian()) { if (!reg.isBigEndian() && !reg.isProcessorContext()) {
ArrayUtils.reverse(arr, startVal, startVal + byteLength); ArrayUtils.reverse(arr, mask.length, buf.capacity());
}
return new RegisterValue(reg, arr);
}
public static boolean isByteBound(Register register) {
return register.getLeastSignificantBit() % 8 == 0 && register.getBitLength() % 8 == 0;
}
public static void requireByteBound(Register register) {
if (!isByteBound(register)) {
throw new IllegalArgumentException(
"Cannot work with sub-byte registers. Consider a parent, instead.");
} }
return new RegisterValue(register, arr);
} }
} }

View file

@ -49,6 +49,8 @@ import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.symbol.DBTraceReference; import ghidra.trace.database.symbol.DBTraceReference;
import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.language.TraceGuestLanguage; import ghidra.trace.model.language.TraceGuestLanguage;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.database.DBOpenMode; import ghidra.util.database.DBOpenMode;
@ -94,6 +96,10 @@ public class ToyDBTraceBuilder implements AutoCloseable {
return new AddressRangeImpl(addr(start), addr(end)); return new AddressRangeImpl(addr(start), addr(end));
} }
public TraceAddressSnapRange srange(long snap, long start, long end) {
return new ImmutableTraceAddressSnapRange(addr(start), addr(end), snap, snap);
}
public AddressRange drng(long start, long end) { public AddressRange drng(long start, long end) {
return new AddressRangeImpl(data(start), data(end)); return new AddressRangeImpl(data(start), data(end));
} }

View file

@ -15,11 +15,20 @@
*/ */
package ghidra.trace.database.memory; package ghidra.trace.database.memory;
import org.junit.Test;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
public class DBTraceMemoryManagerBETest extends AbstractDBTraceMemoryManagerTest { public class DBTraceMemoryManagerBETest extends AbstractDBTraceMemoryManagerTest {
@Override @Override
protected LanguageID getLanguageID() { protected LanguageID getLanguageID() {
return new LanguageID("Toy:BE:64:default"); return new LanguageID("Toy:BE:64:default");
} }
@TestLanguage("Toy:BE:32:builder")
@Test
public void testRegisterBits() throws Exception {
runTestRegisterBits();
}
} }

View file

@ -15,11 +15,20 @@
*/ */
package ghidra.trace.database.memory; package ghidra.trace.database.memory;
import org.junit.Test;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
public class DBTraceMemoryManagerLETest extends AbstractDBTraceMemoryManagerTest { public class DBTraceMemoryManagerLETest extends AbstractDBTraceMemoryManagerTest {
@Override @Override
protected LanguageID getLanguageID() { protected LanguageID getLanguageID() {
return new LanguageID("Toy:LE:64:default"); return new LanguageID("Toy:LE:64:default");
} }
@TestLanguage("Toy:LE:32:builder")
@Test
public void testRegisterBits() throws Exception {
runTestRegisterBits();
}
} }

View file

@ -19,12 +19,9 @@ import static org.junit.Assert.assertEquals;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.*;
import java.util.Set; import java.util.Set;
import org.junit.*; import org.junit.*;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import com.google.common.collect.Range; import com.google.common.collect.Range;
@ -45,6 +42,8 @@ import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceOverlappedRegionException; import ghidra.trace.model.memory.TraceOverlappedRegionException;
import ghidra.trace.util.LanguageTestWatcher;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.ConsoleTaskMonitor;
@ -54,35 +53,8 @@ public class DBTraceDisassemblerIntegrationTest extends AbstractGhidraHeadlessIn
protected ToyDBTraceBuilder b; protected ToyDBTraceBuilder b;
protected DBTraceVariableSnapProgramView view; protected DBTraceVariableSnapProgramView view;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLanguage {
String value();
}
public static class LanguageWatcher extends TestWatcher {
String language = ProgramBuilder._TOY64_BE;
@Override
protected void starting(Description description) {
language = computeLanguage(description);
}
private String computeLanguage(Description description) {
TestLanguage annot = description.getAnnotation(TestLanguage.class);
if (annot == null) {
return ProgramBuilder._TOY64_BE;
}
return annot.value();
}
public String getLanguage() {
return language;
}
}
@Rule @Rule
public LanguageWatcher testLanguage = new LanguageWatcher(); public LanguageTestWatcher testLanguage = new LanguageTestWatcher();
@Before @Before
public void setUp() throws IOException { public void setUp() throws IOException {

View file

@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.util;
import java.lang.annotation.*;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import ghidra.program.database.ProgramBuilder;
public class LanguageTestWatcher extends TestWatcher {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLanguage {
String value();
}
protected String language;
public LanguageTestWatcher() {
this(ProgramBuilder._TOY64_BE);
}
public LanguageTestWatcher(String defaultLanguage) {
this.language = defaultLanguage;
}
@Override
protected void starting(Description description) {
TestLanguage annot = description.getAnnotation(TestLanguage.class);
if (annot == null) {
return;
}
language = annot.value();
}
public String getLanguage() {
return language;
}
}

View file

@ -21,11 +21,10 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
/** /**
* Class to represent a processor register. To sort of handle bit registers, a * Class to represent a processor register. To sort of handle bit registers, a special addressing
* special addressing convention is used. First the upper bit is set. Second, the * convention is used. First the upper bit is set. Second, the next 3 bits are used to specify what
* next 3 bits are used to specify what bit position within a byte that this register * bit position within a byte that this register bit exists at. Finally, the rest of the address is
* bit exists at. Finally, the rest of the address is the address of the byte where * the address of the byte where the register bit lives.
* the register bit lives.
*/ */
public class Register implements java.io.Serializable, Comparable<Register> { public class Register implements java.io.Serializable, Comparable<Register> {
@ -61,7 +60,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
private Register baseRegister; private Register baseRegister;
private String group; private String group;
/**Set of valid lane sizes**/ /** Set of valid lane sizes **/
private TreeSet<Integer> laneSizes; private TreeSet<Integer> laneSizes;
/** /**
@ -72,10 +71,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
* @param address the address in register space of this register * @param address the address in register space of this register
* @param numBytes the size (in bytes) of this register * @param numBytes the size (in bytes) of this register
* @param bigEndian true if the most significant bytes are associated with the lowest register * @param bigEndian true if the most significant bytes are associated with the lowest register
* addresses, and false if the least significant bytes are associated with the lowest register * addresses, and false if the least significant bytes are associated with the lowest
* addresses. * register addresses.
* @param typeFlags the type(s) of this Register (TYPE_NONE, TYPE_FP, TYPE_SP, * @param typeFlags the type(s) of this Register (TYPE_NONE, TYPE_FP, TYPE_SP, TYPE_PC,
* TYPE_PC, TYPE_CONTEXT, TYPE_ZERO);) * TYPE_CONTEXT, TYPE_ZERO);)
*/ */
public Register(String name, String description, Address address, int numBytes, public Register(String name, String description, Address address, int numBytes,
boolean bigEndian, int typeFlags) { boolean bigEndian, int typeFlags) {
@ -128,6 +127,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Add register alias * Add register alias
*
* @param aliasReg * @param aliasReg
*/ */
void addAlias(String alias) { void addAlias(String alias) {
@ -142,6 +142,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Remove register alias * Remove register alias
*
* @param alias * @param alias
*/ */
void removeAlias(String alias) { void removeAlias(String alias) {
@ -151,9 +152,8 @@ public class Register implements java.io.Serializable, Comparable<Register> {
} }
/** /**
* Return register aliases. * Return register aliases. NOTE: This is generally only supported for context register fields.
* NOTE: This is generally only supported for *
* context register fields.
* @return register aliases or null * @return register aliases or null
*/ */
public Iterable<String> getAliases() { public Iterable<String> getAliases() {
@ -201,6 +201,19 @@ public class Register implements java.io.Serializable, Comparable<Register> {
return (bitLength + 7) / 8; return (bitLength + 7) / 8;
} }
/**
* Returns the number of bytes spanned by this Register.
*
* <p>
* Compare to {{@link #getMinimumByteSize()}: Suppose a 5-bit register spans 2 bytes: 1 bit in
* the first byte, and the remaining 4 in the following byte. Its value can still be stored in 1
* byte, which is what {@link #getMinimumByteSize()} returns; however, its storage still spans 2
* bytes of the base register, which is what this method returns.
*/
public int getNumBytes() {
return numBytes;
}
/** /**
* Returns the offset into the register space for this register * Returns the offset into the register space for this register
*/ */
@ -210,6 +223,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Returns the bit offset from the register address for this register. * Returns the bit offset from the register address for this register.
*
* @return the bit offset from the register address for this register. * @return the bit offset from the register address for this register.
*/ */
public int getLeastSignificantBit() { public int getLeastSignificantBit() {
@ -224,8 +238,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
} }
/** /**
* Returns true for a register whose context value should * Returns true for a register whose context value should follow the disassembly flow.
* follow the disassembly flow.
*/ */
public boolean followsFlow() { public boolean followsFlow() {
return (typeFlags & TYPE_DOES_NOT_FOLLOW_FLOW) == 0; return (typeFlags & TYPE_DOES_NOT_FOLLOW_FLOW) == 0;
@ -335,8 +348,8 @@ public class Register implements java.io.Serializable, Comparable<Register> {
} }
/** /**
* Returns list of children registers sorted by * Returns list of children registers sorted by lest-significant bit-offset within this
* lest-significant bit-offset within this register. * register.
*/ */
public List<Register> getChildRegisters() { public List<Register> getChildRegisters() {
return new ArrayList<>(childRegisters); return new ArrayList<>(childRegisters);
@ -400,6 +413,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Returns the mask that indicates which bits in the base register apply to this register. * Returns the mask that indicates which bits in the base register apply to this register.
*
* @return the mask that indicates which bits in the base register apply to this register * @return the mask that indicates which bits in the base register apply to this register
*/ */
public byte[] getBaseMask() { public byte[] getBaseMask() {
@ -445,11 +459,11 @@ public class Register implements java.io.Serializable, Comparable<Register> {
} }
/** /**
* Determines if reg is contained within this register. * Determines if reg is contained within this register. Method does not work for bit registers
* Method does not work for bit registers (e.g., context-bits) * (e.g., context-bits)
*
* @param reg another register * @param reg another register
* @return true if reg equals this register or is contained * @return true if reg equals this register or is contained within it.
* within it.
*/ */
public boolean contains(Register reg) { public boolean contains(Register reg) {
if (equals(reg)) { if (equals(reg)) {
@ -472,8 +486,9 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Returns true if this is a vector register * Returns true if this is a vector register
* @return true precisely when {@code this} is a full vector register (i.e., a register that can be *
* used as input or output for a SIMD operation). * @return true precisely when {@code this} is a full vector register (i.e., a register that can
* be used as input or output for a SIMD operation).
*/ */
public boolean isVectorRegister() { public boolean isVectorRegister() {
return (typeFlags & TYPE_VECTOR) != 0; return (typeFlags & TYPE_VECTOR) != 0;
@ -481,8 +496,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Determines whether {@code laneSizeInBytes} is a valid lane size for this register. * Determines whether {@code laneSizeInBytes} is a valid lane size for this register.
*
* @param laneSizeInBytes lane size to check, measured in bytes * @param laneSizeInBytes lane size to check, measured in bytes
* @return true precisely when {@code this} is a vector register and {@code laneSizeInBytes} is a valid lane size. * @return true precisely when {@code this} is a vector register and {@code laneSizeInBytes} is
* a valid lane size.
*/ */
public boolean isValidLaneSize(int laneSizeInBytes) { public boolean isValidLaneSize(int laneSizeInBytes) {
if (!isVectorRegister()) { if (!isVectorRegister()) {
@ -496,7 +513,9 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Returns the sorted array of lane sizes for this register, measured in bytes. * Returns the sorted array of lane sizes for this register, measured in bytes.
* @return array of lane sizes, or {@code null} if {@code this} is not a vector register or no lane sizes have been set. *
* @return array of lane sizes, or {@code null} if {@code this} is not a vector register or no
* lane sizes have been set.
*/ */
public int[] getLaneSizes() { public int[] getLaneSizes() {
if (laneSizes == null) { if (laneSizes == null) {
@ -512,9 +531,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/** /**
* Adds a lane size. * Adds a lane size.
*
* @param laneSizeInBytes lane size to add * @param laneSizeInBytes lane size to add
* @throws UnsupportedOperationException if register is unable to support the definition of * @throws UnsupportedOperationException if register is unable to support the definition of
* lanes. * lanes.
* @throws IllegalArgumentException if {@code laneSizeInBytes} is invalid * @throws IllegalArgumentException if {@code laneSizeInBytes} is invalid
*/ */
void addLaneSize(int laneSizeInBytes) { void addLaneSize(int laneSizeInBytes) {