/*
* Copyright (c) 2020, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.internal.foreign.abi;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
import jdk.internal.foreign.MemoryAddressImpl;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.invoke.MethodType.methodType;
The binding operators defined in the Binding class can be combined into argument and return value processing 'recipes'.
The binding operators are interpreted using a stack-base interpreter. Operators can either consume operands from the
stack, or push them onto the stack.
In the description of each binding we talk about 'boxing' and 'unboxing'.
- Unboxing is the process of taking a Java value and decomposing it, and storing components into machine
storage locations. As such, the binding interpreter stack starts with the Java value on it, and should end empty.
- Boxing is the process of re-composing a Java value by pulling components from machine storage locations.
If a MemorySegment is needed to store the result, one should be allocated using the ALLOCATE_BUFFER operator.
The binding interpreter stack starts off empty, and ends with the value to be returned as the only value on it.
A binding operator can be interpreted differently based on whether we are boxing or unboxing a value. For example,
the CONVERT_ADDRESS operator 'unboxes' a MemoryAddress to a long, but 'boxes' a long to a MemoryAddress.
Here are some examples of binding recipes derived from C declarations, and according to the Windows ABI (recipes are
ABI-specific). Note that each argument has it's own recipe, which is indicated by '[number]:' (though, the only
example that has multiple arguments is the one using varargs).
--------------------
void f(int i);
Argument bindings:
0: VM_STORE(rcx, int.class) // move an 'int' into the RCX register
Return bindings:
none
--------------------
void f(int* i);
Argument bindings:
0: UNBOX_ADDRESS // the 'MemoryAddress' is converted into a 'long'
VM_STORE(rcx, long.class) // the 'long' is moved into the RCX register
Return bindings:
none
--------------------
int* f();
Argument bindings:
none
Return bindings:
0: VM_LOAD(rax, long) // load a 'long' from the RAX register
BOX_ADDRESS // convert the 'long' into a 'MemoryAddress'
--------------------
typedef struct { // fits into single register
int x;
int y;
} MyStruct;
void f(MyStruct ms);
Argument bindings:
0: BUFFER_LOAD(0, long.class) // From the struct's memory region, load a 'long' from offset '0'
VM_STORE(rcx, long.class) // and copy that into the RCX register
Return bindings:
none
--------------------
typedef struct { // does not fit into single register
long long x;
long long y;
} MyStruct;
void f(MyStruct ms);
For the Windows ABI:
Argument bindings:
0: COPY(16, 8) // copy the memory region containing the struct
BASE_ADDRESS // take the base address of the copy
UNBOX_ADDRESS // converts the base address to a 'long'
VM_STORE(rcx, long.class) // moves the 'long' into the RCX register
Return bindings:
none
For the SysV ABI:
Argument bindings:
0: DUP // duplicates the MemoryRegion operand
BUFFER_LOAD(0, long.class) // loads a 'long' from offset '0'
VM_STORE(rdx, long.class) // moves the long into the RDX register
BUFFER_LOAD(8, long.class) // loads a 'long' from offset '8'
VM_STORE(rcx, long.class) // moves the long into the RCX register
Return bindings:
none
--------------------
typedef struct { // fits into single register
int x;
int y;
} MyStruct;
MyStruct f();
Argument bindings:
none
Return bindings:
0: ALLOCATE(GroupLayout(C_INT, C_INT)) // allocate a buffer with the memory layout of the struct
DUP // duplicate the allocated buffer
VM_LOAD(rax, long.class) // loads a 'long' from rax
BUFFER_STORE(0, long.class) // stores a 'long' at offset 0
--------------------
typedef struct { // does not fit into single register
long long x;
long long y;
} MyStruct;
MyStruct f();
!! uses synthetic argument, which is a pointer to a pre-allocated buffer
Argument bindings:
0: UNBOX_ADDRESS // unbox the MemoryAddress synthetic argument
VM_STORE(rcx, long.class) // moves the 'long' into the RCX register
Return bindings:
none
--------------------
void f(int dummy, ...); // varargs
f(0, 10f); // passing a float
Argument bindings:
0: VM_STORE(rcx, int.class) // moves the 'int dummy' into the RCX register
1: DUP // duplicates the '10f' argument
VM_STORE(rdx, float.class) // move one copy into the RDX register
VM_STORE(xmm1, float.class) // moves the other copy into the xmm2 register
Return bindings:
none
--------------------
/**
* The binding operators defined in the Binding class can be combined into argument and return value processing 'recipes'.
*
* The binding operators are interpreted using a stack-base interpreter. Operators can either consume operands from the
* stack, or push them onto the stack.
*
* In the description of each binding we talk about 'boxing' and 'unboxing'.
* - Unboxing is the process of taking a Java value and decomposing it, and storing components into machine
* storage locations. As such, the binding interpreter stack starts with the Java value on it, and should end empty.
* - Boxing is the process of re-composing a Java value by pulling components from machine storage locations.
* If a MemorySegment is needed to store the result, one should be allocated using the ALLOCATE_BUFFER operator.
* The binding interpreter stack starts off empty, and ends with the value to be returned as the only value on it.
* A binding operator can be interpreted differently based on whether we are boxing or unboxing a value. For example,
* the CONVERT_ADDRESS operator 'unboxes' a MemoryAddress to a long, but 'boxes' a long to a MemoryAddress.
*
* Here are some examples of binding recipes derived from C declarations, and according to the Windows ABI (recipes are
* ABI-specific). Note that each argument has it's own recipe, which is indicated by '[number]:' (though, the only
* example that has multiple arguments is the one using varargs).
*
* --------------------
*
* void f(int i);
*
* Argument bindings:
* 0: VM_STORE(rcx, int.class) // move an 'int' into the RCX register
*
* Return bindings:
* none
*
* --------------------
*
* void f(int* i);
*
* Argument bindings:
* 0: UNBOX_ADDRESS // the 'MemoryAddress' is converted into a 'long'
* VM_STORE(rcx, long.class) // the 'long' is moved into the RCX register
*
* Return bindings:
* none
*
* --------------------
*
* int* f();
*
* Argument bindings:
* none
*
* Return bindings:
* 0: VM_LOAD(rax, long) // load a 'long' from the RAX register
* BOX_ADDRESS // convert the 'long' into a 'MemoryAddress'
*
* --------------------
*
* typedef struct { // fits into single register
* int x;
* int y;
* } MyStruct;
*
* void f(MyStruct ms);
*
* Argument bindings:
* 0: BUFFER_LOAD(0, long.class) // From the struct's memory region, load a 'long' from offset '0'
* VM_STORE(rcx, long.class) // and copy that into the RCX register
*
* Return bindings:
* none
*
* --------------------
*
* typedef struct { // does not fit into single register
* long long x;
* long long y;
* } MyStruct;
*
* void f(MyStruct ms);
*
* For the Windows ABI:
*
* Argument bindings:
* 0: COPY(16, 8) // copy the memory region containing the struct
* BASE_ADDRESS // take the base address of the copy
* UNBOX_ADDRESS // converts the base address to a 'long'
* VM_STORE(rcx, long.class) // moves the 'long' into the RCX register
*
* Return bindings:
* none
*
* For the SysV ABI:
*
* Argument bindings:
* 0: DUP // duplicates the MemoryRegion operand
* BUFFER_LOAD(0, long.class) // loads a 'long' from offset '0'
* VM_STORE(rdx, long.class) // moves the long into the RDX register
* BUFFER_LOAD(8, long.class) // loads a 'long' from offset '8'
* VM_STORE(rcx, long.class) // moves the long into the RCX register
*
* Return bindings:
* none
*
* --------------------
*
* typedef struct { // fits into single register
* int x;
* int y;
* } MyStruct;
*
* MyStruct f();
*
* Argument bindings:
* none
*
* Return bindings:
* 0: ALLOCATE(GroupLayout(C_INT, C_INT)) // allocate a buffer with the memory layout of the struct
* DUP // duplicate the allocated buffer
* VM_LOAD(rax, long.class) // loads a 'long' from rax
* BUFFER_STORE(0, long.class) // stores a 'long' at offset 0
*
* --------------------
*
* typedef struct { // does not fit into single register
* long long x;
* long long y;
* } MyStruct;
*
* MyStruct f();
*
* !! uses synthetic argument, which is a pointer to a pre-allocated buffer
*
* Argument bindings:
* 0: UNBOX_ADDRESS // unbox the MemoryAddress synthetic argument
* VM_STORE(rcx, long.class) // moves the 'long' into the RCX register
*
* Return bindings:
* none
*
* --------------------
*
* void f(int dummy, ...); // varargs
*
* f(0, 10f); // passing a float
*
* Argument bindings:
* 0: VM_STORE(rcx, int.class) // moves the 'int dummy' into the RCX register
*
* 1: DUP // duplicates the '10f' argument
* VM_STORE(rdx, float.class) // move one copy into the RDX register
* VM_STORE(xmm1, float.class) // moves the other copy into the xmm2 register
*
* Return bindings:
* none
*
* --------------------
*/
public abstract class Binding {
private static final MethodHandle MH_UNBOX_ADDRESS;
private static final MethodHandle MH_BOX_ADDRESS;
private static final MethodHandle MH_BASE_ADDRESS;
private static final MethodHandle MH_COPY_BUFFER;
private static final MethodHandle MH_ALLOCATE_BUFFER;
private static final MethodHandle MH_TO_SEGMENT;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_UNBOX_ADDRESS = lookup.findVirtual(MemoryAddress.class, "toRawLongValue",
methodType(long.class));
MH_BOX_ADDRESS = lookup.findStatic(MemoryAddress.class, "ofLong",
methodType(MemoryAddress.class, long.class));
MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "address",
methodType(MemoryAddress.class));
MH_COPY_BUFFER = lookup.findStatic(Binding.Copy.class, "copyBuffer",
methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class));
MH_ALLOCATE_BUFFER = lookup.findStatic(Binding.Allocate.class, "allocateBuffer",
methodType(MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class));
MH_TO_SEGMENT = lookup.findStatic(Binding.ToSegment.class, "toSegment",
methodType(MemorySegment.class, MemoryAddress.class, long.class));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
enum Tag {
VM_STORE,
VM_LOAD,
BUFFER_STORE,
BUFFER_LOAD,
COPY_BUFFER,
ALLOC_BUFFER,
BOX_ADDRESS,
UNBOX_ADDRESS,
BASE_ADDRESS,
TO_SEGMENT,
DUP
}
private final Tag tag;
private Binding(Tag tag) {
this.tag = tag;
}
public Tag tag() {
return tag;
}
public abstract void verify(Deque<Class<?>> stack);
public abstract void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator);
public abstract MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos);
private static MethodHandle mergeArguments(MethodHandle mh, int sourceIndex, int destIndex) {
MethodType oldType = mh.type();
Class<?> sourceType = oldType.parameterType(sourceIndex);
Class<?> destType = oldType.parameterType(destIndex);
if (sourceType != destType) {
// TODO meet?
throw new IllegalArgumentException("Parameter types differ: " + sourceType + " != " + destType);
}
MethodType newType = oldType.dropParameterTypes(destIndex, destIndex + 1);
int[] reorder = new int[oldType.parameterCount()];
assert destIndex > sourceIndex;
for (int i = 0, index = 0; i < reorder.length; i++) {
if (i != destIndex) {
reorder[i] = index++;
} else {
reorder[i] = sourceIndex;
}
}
return permuteArguments(mh, newType, reorder);
}
private static void checkType(Class<?> type) {
if (!type.isPrimitive() || type == void.class || type == boolean.class)
throw new IllegalArgumentException("Illegal type: " + type);
}
private static void checkOffset(long offset) {
if (offset < 0)
throw new IllegalArgumentException("Negative offset: " + offset);
}
public static VMStore vmStore(VMStorage storage, Class<?> type) {
checkType(type);
return new VMStore(storage, type);
}
public static VMLoad vmLoad(VMStorage storage, Class<?> type) {
checkType(type);
return new VMLoad(storage, type);
}
public static BufferStore bufferStore(long offset, Class<?> type) {
checkType(type);
checkOffset(offset);
return new BufferStore(offset, type);
}
public static BufferLoad bufferLoad(long offset, Class<?> type) {
checkType(type);
checkOffset(offset);
return new BufferLoad(offset, type);
}
public static Copy copy(MemoryLayout layout) {
return new Copy(layout.byteSize(), layout.byteAlignment());
}
public static Allocate allocate(MemoryLayout layout) {
return new Allocate(layout.byteSize(), layout.byteAlignment());
}
public static BoxAddress boxAddress() {
return BoxAddress.INSTANCE;
}
public static UnboxAddress unboxAddress() {
return UnboxAddress.INSTANCE;
}
public static BaseAddress baseAddress() {
return BaseAddress.INSTANCE;
}
public static ToSegment toSegment(MemoryLayout layout) {
return new ToSegment(layout.byteSize());
}
public static Dup dup() {
return Dup.INSTANCE;
}
public static Binding.Builder builder() {
return new Binding.Builder();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Binding binding = (Binding) o;
return tag == binding.tag;
}
@Override
public int hashCode() {
return Objects.hash(tag);
}
A builder helper class for generating lists of Bindings
/**
* A builder helper class for generating lists of Bindings
*/
public static class Builder {
private final List<Binding> bindings = new ArrayList<>();
public Binding.Builder vmStore(VMStorage storage, Class<?> type) {
bindings.add(Binding.vmStore(storage, type));
return this;
}
public Binding.Builder vmLoad(VMStorage storage, Class<?> type) {
bindings.add(Binding.vmLoad(storage, type));
return this;
}
public Binding.Builder bufferStore(long offset, Class<?> type) {
bindings.add(Binding.bufferStore(offset, type));
return this;
}
public Binding.Builder bufferLoad(long offset, Class<?> type) {
bindings.add(Binding.bufferLoad(offset, type));
return this;
}
public Binding.Builder copy(MemoryLayout layout) {
bindings.add(Binding.copy(layout));
return this;
}
public Binding.Builder allocate(MemoryLayout layout) {
bindings.add(Binding.allocate(layout));
return this;
}
public Binding.Builder boxAddress() {
bindings.add(Binding.boxAddress());
return this;
}
public Binding.Builder unboxAddress() {
bindings.add(Binding.unboxAddress());
return this;
}
public Binding.Builder baseAddress() {
bindings.add(Binding.baseAddress());
return this;
}
public Binding.Builder toSegment(MemoryLayout layout) {
bindings.add(Binding.toSegment(layout));
return this;
}
public Binding.Builder dup() {
bindings.add(Binding.dup());
return this;
}
public List<Binding> build() {
return new ArrayList<>(bindings);
}
}
static abstract class Move extends Binding {
private final VMStorage storage;
private final Class<?> type;
private Move(Tag tag, VMStorage storage, Class<?> type) {
super(tag);
this.storage = storage;
this.type = type;
}
public VMStorage storage() {
return storage;
}
public Class<?> type() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Move move = (Move) o;
return Objects.equals(storage, move.storage) &&
Objects.equals(type, move.type);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), storage, type);
}
}
VM_STORE([storage location], [type])
Pops a [type] from the operand stack, and moves it to [storage location]
The [type] must be one of byte, short, char, int, long, float, or double
/**
* VM_STORE([storage location], [type])
* Pops a [type] from the operand stack, and moves it to [storage location]
* The [type] must be one of byte, short, char, int, long, float, or double
*/
public static class VMStore extends Move {
private VMStore(VMStorage storage, Class<?> type) {
super(Tag.VM_STORE, storage, type);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
Class<?> expectedType = type();
SharedUtils.checkType(actualType, expectedType);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
storeFunc.store(storage(), type(), stack.pop());
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return specializedHandle; // no-op
}
@Override
public String toString() {
return "VMStore{" +
"storage=" + storage() +
", type=" + type() +
'}';
}
}
VM_LOAD([storage location], [type])
Loads a [type] from [storage location], and pushes it onto the operand stack.
The [type] must be one of byte, short, char, int, long, float, or double
/**
* VM_LOAD([storage location], [type])
* Loads a [type] from [storage location], and pushes it onto the operand stack.
* The [type] must be one of byte, short, char, int, long, float, or double
*/
public static class VMLoad extends Move {
private VMLoad(VMStorage storage, Class<?> type) {
super(Tag.VM_LOAD, storage, type);
}
@Override
public void verify(Deque<Class<?>> stack) {
stack.push(type());
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(loadFunc.load(storage(), type()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return specializedHandle; // no-op
}
@Override
public String toString() {
return "VMLoad{" +
"storage=" + storage() +
", type=" + type() +
'}';
}
}
private static abstract class Dereference extends Binding {
private final long offset;
private final Class<?> type;
private Dereference(Tag tag, long offset, Class<?> type) {
super(tag);
this.offset = offset;
this.type = type;
}
public long offset() {
return offset;
}
public Class<?> type() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Dereference that = (Dereference) o;
return offset == that.offset &&
Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), offset, type);
}
public VarHandle varHandle() {
return MemoryHandles.insertCoordinates(MemoryHandles.varHandle(type, ByteOrder.nativeOrder()), 1, offset);
}
}
BUFFER_STORE([offset into memory region], [type])
Pops a MemorySegment from the operand stack, loads a [type] from
[offset into memory region] from it, and pushes it onto the operand stack.
The [type] must be one of byte, short, char, int, long, float, or double
/**
* BUFFER_STORE([offset into memory region], [type])
* Pops a MemorySegment from the operand stack, loads a [type] from
* [offset into memory region] from it, and pushes it onto the operand stack.
* The [type] must be one of byte, short, char, int, long, float, or double
*/
public static class BufferStore extends Dereference {
private BufferStore(long offset, Class<?> type) {
super(Tag.BUFFER_STORE, offset, type);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> storeType = stack.pop();
SharedUtils.checkType(storeType, type());
Class<?> segmentType = stack.pop();
SharedUtils.checkType(segmentType, MemorySegment.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
Object value = stack.pop();
MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment writeAddress = operand.asSlice(offset());
SharedUtils.write(writeAddress, type(), value);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle setter = varHandle().toMethodHandle(VarHandle.AccessMode.SET);
setter = setter.asType(methodType(void.class, MemorySegment.class, type()));
return collectArguments(specializedHandle, insertPos + 1, setter);
}
@Override
public String toString() {
return "BufferStore{" +
"offset=" + offset() +
", type=" + type() +
'}';
}
}
BUFFER_LOAD([offset into memory region], [type])
Pops a [type], and then a MemorySegment from the operand stack,
and then stores [type] to [offset into memory region] of the MemorySegment.
The [type] must be one of byte, short, char, int, long, float, or double
/**
* BUFFER_LOAD([offset into memory region], [type])
* Pops a [type], and then a MemorySegment from the operand stack,
* and then stores [type] to [offset into memory region] of the MemorySegment.
* The [type] must be one of byte, short, char, int, long, float, or double
*/
public static class BufferLoad extends Dereference {
private BufferLoad(long offset, Class<?> type) {
super(Tag.BUFFER_LOAD, offset, type);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemorySegment.class);
Class<?> newType = type();
stack.push(newType);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment readAddress = operand.asSlice(offset());
stack.push(SharedUtils.read(readAddress, type()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle filter = varHandle()
.toMethodHandle(VarHandle.AccessMode.GET)
.asType(methodType(type(), MemorySegment.class));
return filterArguments(specializedHandle, insertPos, filter);
}
@Override
public String toString() {
return "BufferLoad{" +
"offset=" + offset() +
", type=" + type() +
'}';
}
}
COPY([size], [alignment])
Creates a new MemorySegment with the given [size] and [alignment],
and copies contents from a MemorySegment popped from the top of the operand stack into this new buffer,
and pushes the new buffer onto the operand stack
/**
* COPY([size], [alignment])
* Creates a new MemorySegment with the given [size] and [alignment],
* and copies contents from a MemorySegment popped from the top of the operand stack into this new buffer,
* and pushes the new buffer onto the operand stack
*/
public static class Copy extends Binding {
private final long size;
private final long alignment;
private Copy(long size, long alignment) {
super(Tag.COPY_BUFFER);
this.size = size;
this.alignment = alignment;
}
private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment,
SharedUtils.Allocator allocator) {
MemorySegment copy = allocator.allocate(size, alignment);
copy.copyFrom(operand.asSlice(0, size));
return copy;
}
public long size() {
return size;
}
public long alignment() {
return alignment;
}
@Override
public String toString() {
return "Copy{" +
"tag=" + tag() +
", size=" + size +
", alignment=" + alignment +
'}';
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemorySegment.class);
stack.push(MemorySegment.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment copy = copyBuffer(operand, size, alignment, allocator);
stack.push(copy);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, filter);
return mergeArguments(specializedHandle, allocatorPos, insertPos + 1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Copy copy = (Copy) o;
return size == copy.size &&
alignment == copy.alignment;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), size, alignment);
}
}
ALLOCATE([size], [alignment])
Creates a new MemorySegment with the give [size] and [alignment], and pushes it onto the operand stack.
/**
* ALLOCATE([size], [alignment])
* Creates a new MemorySegment with the give [size] and [alignment], and pushes it onto the operand stack.
*/
public static class Allocate extends Binding {
private final long size;
private final long alignment;
private Allocate(long size, long alignment) {
super(Tag.ALLOC_BUFFER);
this.size = size;
this.alignment = alignment;
}
private static MemorySegment allocateBuffer(long size, long allignment, SharedUtils.Allocator allocator) {
return allocator.allocate(size, allignment);
}
public long size() {
return size;
}
public long alignment() {
return alignment;
}
@Override
public String toString() {
return "AllocateBuffer{" +
"tag=" + tag() +
"size=" + size +
", alignment=" + alignment +
'}';
}
@Override
public void verify(Deque<Class<?>> stack) {
stack.push(MemorySegment.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(allocateBuffer(size, alignment, allocator));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle allocateBuffer = insertArguments(MH_ALLOCATE_BUFFER, 0, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, allocateBuffer);
return mergeArguments(specializedHandle, allocatorPos, insertPos);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Allocate allocate = (Allocate) o;
return size == allocate.size &&
alignment == allocate.alignment;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), size, alignment);
}
}
UNBOX_ADDRESS()
Pops a 'MemoryAddress' from the operand stack, converts it to a 'long',
and pushes that onto the operand stack.
/**
* UNBOX_ADDRESS()
* Pops a 'MemoryAddress' from the operand stack, converts it to a 'long',
* and pushes that onto the operand stack.
*/
public static class UnboxAddress extends Binding {
private static final UnboxAddress INSTANCE = new UnboxAddress();
private UnboxAddress() {
super(Tag.UNBOX_ADDRESS);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemoryAddress.class);
stack.push(long.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(((MemoryAddress)stack.pop()).toRawLongValue());
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return filterArguments(specializedHandle, insertPos, MH_UNBOX_ADDRESS);
}
@Override
public String toString() {
return "UnboxAddress{}";
}
}
Box_ADDRESS()
Pops a 'long' from the operand stack, converts it to a 'MemoryAddress',
and pushes that onto the operand stack.
/**
* Box_ADDRESS()
* Pops a 'long' from the operand stack, converts it to a 'MemoryAddress',
* and pushes that onto the operand stack.
*/
public static class BoxAddress extends Binding {
private static final BoxAddress INSTANCE = new BoxAddress();
private BoxAddress() {
super(Tag.BOX_ADDRESS);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, long.class);
stack.push(MemoryAddress.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(MemoryAddress.ofLong((long) stack.pop()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return filterArguments(specializedHandle, insertPos, MH_BOX_ADDRESS);
}
@Override
public String toString() {
return "BoxAddress{}";
}
}
BASE_ADDRESS()
Pops a MemorySegment from the operand stack, and takes the base address of the segment
(the MemoryAddress that points to the start), and pushes that onto the operand stack
/**
* BASE_ADDRESS()
* Pops a MemorySegment from the operand stack, and takes the base address of the segment
* (the MemoryAddress that points to the start), and pushes that onto the operand stack
*/
public static class BaseAddress extends Binding {
private static final BaseAddress INSTANCE = new BaseAddress();
private BaseAddress() {
super(Tag.BASE_ADDRESS);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemorySegment.class);
stack.push(MemoryAddress.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(((MemorySegment) stack.pop()).address());
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return filterArguments(specializedHandle, insertPos, MH_BASE_ADDRESS);
}
@Override
public String toString() {
return "BaseAddress{}";
}
}
BASE_ADDRESS([size])
Pops a MemoryAddress from the operand stack, and takes the converts it to a MemorySegment
with the given size, and pushes that onto the operand stack
/**
* BASE_ADDRESS([size])
* Pops a MemoryAddress from the operand stack, and takes the converts it to a MemorySegment
* with the given size, and pushes that onto the operand stack
*/
public static class ToSegment extends Binding {
private final long size;
// FIXME alignment?
public ToSegment(long size) {
super(Tag.TO_SEGMENT);
this.size = size;
}
// FIXME should register with scope
private static MemorySegment toSegment(MemoryAddress operand, long size) {
return MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), size);
}
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemoryAddress.class);
stack.push(MemorySegment.class);
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
MemoryAddress operand = (MemoryAddress) stack.pop();
MemorySegment segment = toSegment(operand, size);
stack.push(segment);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle toSegmentHandle = insertArguments(MH_TO_SEGMENT, 1, size);
return filterArguments(specializedHandle, insertPos, toSegmentHandle);
}
@Override
public String toString() {
return "ToSegemnt{" +
"size=" + size +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ToSegment toSegemnt = (ToSegment) o;
return size == toSegemnt.size;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), size);
}
}
DUP()
Duplicates the value on the top of the operand stack (without popping it!),
and pushes the duplicate onto the operand stack
/**
* DUP()
* Duplicates the value on the top of the operand stack (without popping it!),
* and pushes the duplicate onto the operand stack
*/
public static class Dup extends Binding {
private static final Dup INSTANCE = new Dup();
private Dup() {
super(Tag.DUP);
}
@Override
public void verify(Deque<Class<?>> stack) {
stack.push(stack.peekLast());
}
@Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) {
stack.push(stack.peekLast());
}
/*
* Fixes up Y-shaped data graphs (produced by DEREFERENCE):
*
* 1. DUP()
* 2. BUFFER_LOAD(0, int.class)
* 3. VM_STORE (ignored)
* 4. BUFFER_LOAD(4, int.class)
* 5. VM_STORE (ignored)
*
* (specialized in reverse!)
*
* 5. (int, int) -> void insertPos = 1
* 4. (MemorySegment, int) -> void insertPos = 1
* 3. (MemorySegment, int) -> void insertPos = 0
* 2. (MemorySegment, MemorySegment) -> void insertPos = 0
* 1. (MemorySegment) -> void insertPos = 0
*
*/
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return mergeArguments(specializedHandle, insertPos, insertPos + 1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o != null && getClass() == o.getClass();
}
@Override
public String toString() {
return "Dup{}";
}
}
}