8264774: Implementation of Foreign Function and Memory API (Incubator)

Co-authored-by: Paul Sandoz <psandoz@openjdk.org>
Co-authored-by: Jorn Vernee <jvernee@openjdk.org>
Co-authored-by: Vladimir Ivanov <vlivanov@openjdk.org>
Co-authored-by: Athijegannathan Sundararajan <sundar@openjdk.org>
Co-authored-by: Chris Hegarty <chegar@openjdk.org>
Reviewed-by: psandoz, chegar, mchung, vlivanov
This commit is contained in:
Maurizio Cimadamore 2021-06-02 10:53:06 +00:00
parent 71425ddfb4
commit a223189b06
219 changed files with 10936 additions and 5695 deletions

View file

@ -84,3 +84,8 @@ const BufferLayout ForeignGlobals::parse_buffer_layout_impl(jobject jlayout) con
return layout; return layout;
} }
const CallRegs ForeignGlobals::parse_call_regs_impl(jobject jconv) const {
ShouldNotCallThis();
return {};
}

View file

@ -359,6 +359,16 @@ frame frame::sender_for_entry_frame(RegisterMap* map) const {
return fr; return fr;
} }
JavaFrameAnchor* OptimizedEntryBlob::jfa_for_frame(const frame& frame) const {
ShouldNotCallThis();
return nullptr;
}
frame frame::sender_for_optimized_entry_frame(RegisterMap* map) const {
ShouldNotCallThis();
return {};
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// frame::verify_deopt_original_pc // frame::verify_deopt_original_pc
// //

View file

@ -878,7 +878,7 @@ int SharedRuntime::c_calling_convention(const BasicType *sig_bt,
// 64 bits items (Aarch64 abi) even though java would only store // 64 bits items (Aarch64 abi) even though java would only store
// 32bits for a parameter. On 32bit it will simply be 32 bits // 32bits for a parameter. On 32bit it will simply be 32 bits
// So this routine will do 32->32 on 32bit and 32->64 on 64bit // So this routine will do 32->32 on 32bit and 32->64 on 64bit
static void move32_64(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::move32_64(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
if (src.first()->is_stack()) { if (src.first()->is_stack()) {
if (dst.first()->is_stack()) { if (dst.first()->is_stack()) {
// stack to stack // stack to stack
@ -979,7 +979,7 @@ static void object_move(MacroAssembler* masm,
} }
// A float arg may have to do float reg int reg conversion // A float arg may have to do float reg int reg conversion
static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
assert(src.first()->is_stack() && dst.first()->is_stack() || assert(src.first()->is_stack() && dst.first()->is_stack() ||
src.first()->is_reg() && dst.first()->is_reg(), "Unexpected error"); src.first()->is_reg() && dst.first()->is_reg(), "Unexpected error");
if (src.first()->is_stack()) { if (src.first()->is_stack()) {
@ -998,7 +998,7 @@ static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
} }
// A long move // A long move
static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
if (src.first()->is_stack()) { if (src.first()->is_stack()) {
if (dst.first()->is_stack()) { if (dst.first()->is_stack()) {
// stack to stack // stack to stack
@ -1022,7 +1022,7 @@ static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
// A double move // A double move
static void double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
assert(src.first()->is_stack() && dst.first()->is_stack() || assert(src.first()->is_stack() && dst.first()->is_stack() ||
src.first()->is_reg() && dst.first()->is_reg(), "Unexpected error"); src.first()->is_reg() && dst.first()->is_reg(), "Unexpected error");
if (src.first()->is_stack()) { if (src.first()->is_stack()) {

View file

@ -99,3 +99,12 @@ address ProgrammableUpcallHandler::generate_upcall_stub(jobject rec, jobject jab
return blob->code_begin(); return blob->code_begin();
} }
address ProgrammableUpcallHandler::generate_optimized_upcall_stub(jobject mh, Method* entry, jobject jabi, jobject jconv) {
ShouldNotCallThis();
return nullptr;
}
bool ProgrammableUpcallHandler::supports_optimized_upcalls() {
return false;
}

View file

@ -87,3 +87,32 @@ const BufferLayout ForeignGlobals::parse_buffer_layout_impl(jobject jlayout) con
return layout; return layout;
} }
const CallRegs ForeignGlobals::parse_call_regs_impl(jobject jconv) const {
oop conv_oop = JNIHandles::resolve_non_null(jconv);
objArrayOop arg_regs_oop = cast<objArrayOop>(conv_oop->obj_field(CallConvOffsets.arg_regs_offset));
objArrayOop ret_regs_oop = cast<objArrayOop>(conv_oop->obj_field(CallConvOffsets.ret_regs_offset));
CallRegs result;
result._args_length = arg_regs_oop->length();
result._arg_regs = NEW_RESOURCE_ARRAY(VMReg, result._args_length);
result._rets_length = ret_regs_oop->length();
result._ret_regs = NEW_RESOURCE_ARRAY(VMReg, result._rets_length);
for (int i = 0; i < result._args_length; i++) {
oop storage = arg_regs_oop->obj_at(i);
jint index = storage->int_field(VMS.index_offset);
jint type = storage->int_field(VMS.type_offset);
result._arg_regs[i] = VMRegImpl::vmStorageToVMReg(type, index);
}
for (int i = 0; i < result._rets_length; i++) {
oop storage = ret_regs_oop->obj_at(i);
jint index = storage->int_field(VMS.index_offset);
jint type = storage->int_field(VMS.type_offset);
result._ret_regs[i] = VMRegImpl::vmStorageToVMReg(type, index);
}
return result;
}

View file

@ -102,6 +102,8 @@ bool frame::safe_for_sender(JavaThread *thread) {
if (is_entry_frame()) { if (is_entry_frame()) {
// an entry frame must have a valid fp. // an entry frame must have a valid fp.
return fp_safe && is_entry_frame_valid(thread); return fp_safe && is_entry_frame_valid(thread);
} else if (is_optimized_entry_frame()) {
return fp_safe;
} }
intptr_t* sender_sp = NULL; intptr_t* sender_sp = NULL;
@ -199,6 +201,8 @@ bool frame::safe_for_sender(JavaThread *thread) {
address jcw = (address)sender.entry_frame_call_wrapper(); address jcw = (address)sender.entry_frame_call_wrapper();
return thread->is_in_stack_range_excl(jcw, (address)sender.fp()); return thread->is_in_stack_range_excl(jcw, (address)sender.fp());
} else if (sender_blob->is_optimized_entry_blob()) {
return false;
} }
CompiledMethod* nm = sender_blob->as_compiled_method_or_null(); CompiledMethod* nm = sender_blob->as_compiled_method_or_null();
@ -349,6 +353,32 @@ frame frame::sender_for_entry_frame(RegisterMap* map) const {
return fr; return fr;
} }
JavaFrameAnchor* OptimizedEntryBlob::jfa_for_frame(const frame& frame) const {
// need unextended_sp here, since normal sp is wrong for interpreter callees
return reinterpret_cast<JavaFrameAnchor*>(reinterpret_cast<char*>(frame.unextended_sp()) + in_bytes(jfa_sp_offset()));
}
frame frame::sender_for_optimized_entry_frame(RegisterMap* map) const {
assert(map != NULL, "map must be set");
OptimizedEntryBlob* blob = _cb->as_optimized_entry_blob();
// Java frame called from C; skip all C frames and return top C
// frame of that chunk as the sender
JavaFrameAnchor* jfa = blob->jfa_for_frame(*this);
assert(jfa->last_Java_sp() > sp(), "must be above this frame on stack");
// Since we are walking the stack now this nested anchor is obviously walkable
// even if it wasn't when it was stacked.
if (!jfa->walkable()) {
// Capture _last_Java_pc (if needed) and mark anchor walkable.
jfa->capture_last_Java_pc();
}
map->clear();
assert(map->include_argument_oops(), "should be set by clear");
vmassert(jfa->last_Java_pc() != NULL, "not walkable");
frame fr(jfa->last_Java_sp(), jfa->last_Java_fp(), jfa->last_Java_pc());
return fr;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// frame::verify_deopt_original_pc // frame::verify_deopt_original_pc
// //
@ -479,6 +509,7 @@ frame frame::sender_raw(RegisterMap* map) const {
map->set_include_argument_oops(false); map->set_include_argument_oops(false);
if (is_entry_frame()) return sender_for_entry_frame(map); if (is_entry_frame()) return sender_for_entry_frame(map);
if (is_optimized_entry_frame()) return sender_for_optimized_entry_frame(map);
if (is_interpreted_frame()) return sender_for_interpreter_frame(map); if (is_interpreted_frame()) return sender_for_interpreter_frame(map);
assert(_cb == CodeCache::find_blob(pc()),"Must be the same"); assert(_cb == CodeCache::find_blob(pc()),"Must be the same");

View file

@ -70,8 +70,6 @@ public:
address last_Java_pc(void) { return _last_Java_pc; } address last_Java_pc(void) { return _last_Java_pc; }
private:
static ByteSize last_Java_fp_offset() { return byte_offset_of(JavaFrameAnchor, _last_Java_fp); } static ByteSize last_Java_fp_offset() { return byte_offset_of(JavaFrameAnchor, _last_Java_fp); }
public: public:

View file

@ -1124,7 +1124,7 @@ static void object_move(MacroAssembler* masm,
} }
// A float arg may have to do float reg int reg conversion // A float arg may have to do float reg int reg conversion
static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
assert(!src.second()->is_valid() && !dst.second()->is_valid(), "bad float_move"); assert(!src.second()->is_valid() && !dst.second()->is_valid(), "bad float_move");
// Because of the calling convention we know that src is either a stack location // Because of the calling convention we know that src is either a stack location
@ -1142,7 +1142,7 @@ static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
} }
// A long move // A long move
static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
// The only legal possibility for a long_move VMRegPair is: // The only legal possibility for a long_move VMRegPair is:
// 1: two stack slots (possibly unaligned) // 1: two stack slots (possibly unaligned)
@ -1161,7 +1161,7 @@ static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
} }
// A double move // A double move
static void double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
// The only legal possibilities for a double_move VMRegPair are: // The only legal possibilities for a double_move VMRegPair are:
// The painful thing here is that like long_move a VMRegPair might be // The painful thing here is that like long_move a VMRegPair might be

View file

@ -1167,7 +1167,7 @@ int SharedRuntime::c_calling_convention(const BasicType *sig_bt,
// 64 bits items (x86_32/64 abi) even though java would only store // 64 bits items (x86_32/64 abi) even though java would only store
// 32bits for a parameter. On 32bit it will simply be 32 bits // 32bits for a parameter. On 32bit it will simply be 32 bits
// So this routine will do 32->32 on 32bit and 32->64 on 64bit // So this routine will do 32->32 on 32bit and 32->64 on 64bit
static void move32_64(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::move32_64(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
if (src.first()->is_stack()) { if (src.first()->is_stack()) {
if (dst.first()->is_stack()) { if (dst.first()->is_stack()) {
// stack to stack // stack to stack
@ -1285,7 +1285,7 @@ static void object_move(MacroAssembler* masm,
} }
// A float arg may have to do float reg int reg conversion // A float arg may have to do float reg int reg conversion
static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
assert(!src.second()->is_valid() && !dst.second()->is_valid(), "bad float_move"); assert(!src.second()->is_valid() && !dst.second()->is_valid(), "bad float_move");
// The calling conventions assures us that each VMregpair is either // The calling conventions assures us that each VMregpair is either
@ -1314,7 +1314,7 @@ static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
} }
// A long move // A long move
static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
// The calling conventions assures us that each VMregpair is either // The calling conventions assures us that each VMregpair is either
// all really one physical register or adjacent stack slots. // all really one physical register or adjacent stack slots.
@ -1339,7 +1339,7 @@ static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
} }
// A double move // A double move
static void double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) { void SharedRuntime::double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst) {
// The calling conventions assures us that each VMregpair is either // The calling conventions assures us that each VMregpair is either
// all really one physical register or adjacent stack slots. // all really one physical register or adjacent stack slots.
@ -1448,13 +1448,13 @@ static void unpack_array_argument(MacroAssembler* masm, VMRegPair reg, BasicType
// load the length relative to the body. // load the length relative to the body.
__ movl(tmp_reg, Address(tmp_reg, arrayOopDesc::length_offset_in_bytes() - __ movl(tmp_reg, Address(tmp_reg, arrayOopDesc::length_offset_in_bytes() -
arrayOopDesc::base_offset_in_bytes(in_elem_type))); arrayOopDesc::base_offset_in_bytes(in_elem_type)));
move32_64(masm, tmp, length_arg); SharedRuntime::move32_64(masm, tmp, length_arg);
__ jmpb(done); __ jmpb(done);
__ bind(is_null); __ bind(is_null);
// Pass zeros // Pass zeros
__ xorptr(tmp_reg, tmp_reg); __ xorptr(tmp_reg, tmp_reg);
move_ptr(masm, tmp, body_arg); move_ptr(masm, tmp, body_arg);
move32_64(masm, tmp, length_arg); SharedRuntime::move32_64(masm, tmp, length_arg);
__ bind(done); __ bind(done);
__ block_comment("} unpack_array_argument"); __ block_comment("} unpack_array_argument");
@ -1541,8 +1541,8 @@ class ComputeMoveOrder: public StackObj {
GrowableArray<MoveOperation*> edges; GrowableArray<MoveOperation*> edges;
public: public:
ComputeMoveOrder(int total_in_args, VMRegPair* in_regs, int total_c_args, VMRegPair* out_regs, ComputeMoveOrder(int total_in_args, const VMRegPair* in_regs, int total_c_args, VMRegPair* out_regs,
BasicType* in_sig_bt, GrowableArray<int>& arg_order, VMRegPair tmp_vmreg) { const BasicType* in_sig_bt, GrowableArray<int>& arg_order, VMRegPair tmp_vmreg) {
// Move operations where the dest is the stack can all be // Move operations where the dest is the stack can all be
// scheduled first since they can't interfere with the other moves. // scheduled first since they can't interfere with the other moves.
for (int i = total_in_args - 1, c_arg = total_c_args - 1; i >= 0; i--, c_arg--) { for (int i = total_in_args - 1, c_arg = total_c_args - 1; i >= 0; i--, c_arg--) {
@ -4087,3 +4087,13 @@ void OptoRuntime::generate_exception_blob() {
_exception_blob = ExceptionBlob::create(&buffer, oop_maps, SimpleRuntimeFrame::framesize >> 1); _exception_blob = ExceptionBlob::create(&buffer, oop_maps, SimpleRuntimeFrame::framesize >> 1);
} }
#endif // COMPILER2 #endif // COMPILER2
void SharedRuntime::compute_move_order(const BasicType* in_sig_bt,
int total_in_args, const VMRegPair* in_regs,
int total_out_args, VMRegPair* out_regs,
GrowableArray<int>& arg_order,
VMRegPair tmp_vmreg) {
ComputeMoveOrder order(total_in_args, in_regs,
total_out_args, out_regs,
in_sig_bt, arg_order, tmp_vmreg);
}

View file

@ -28,3 +28,12 @@ address ProgrammableUpcallHandler::generate_upcall_stub(jobject rec, jobject jab
Unimplemented(); Unimplemented();
return nullptr; return nullptr;
} }
address ProgrammableUpcallHandler::generate_optimized_upcall_stub(jobject mh, Method* entry, jobject jabi, jobject jconv) {
ShouldNotCallThis();
return nullptr;
}
bool ProgrammableUpcallHandler::supports_optimized_upcalls() {
return false;
}

View file

@ -24,8 +24,17 @@
#include "precompiled.hpp" #include "precompiled.hpp"
#include "asm/macroAssembler.hpp" #include "asm/macroAssembler.hpp"
#include "code/codeBlob.hpp" #include "code/codeBlob.hpp"
#include "code/codeBlob.hpp"
#include "code/vmreg.inline.hpp"
#include "compiler/disassembler.hpp"
#include "logging/logStream.hpp"
#include "memory/resourceArea.hpp" #include "memory/resourceArea.hpp"
#include "prims/universalUpcallHandler.hpp" #include "prims/universalUpcallHandler.hpp"
#include "runtime/sharedRuntime.hpp"
#include "runtime/signature.hpp"
#include "runtime/stubRoutines.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/globalDefinitions.hpp"
#define __ _masm-> #define __ _masm->
@ -141,3 +150,701 @@ address ProgrammableUpcallHandler::generate_upcall_stub(jobject rec, jobject jab
return blob->code_begin(); return blob->code_begin();
} }
struct ArgMove {
BasicType bt;
VMRegPair from;
VMRegPair to;
bool is_identity() const {
return from.first() == to.first() && from.second() == to.second();
}
};
static GrowableArray<ArgMove> compute_argument_shuffle(Method* entry, int& out_arg_size_bytes, const CallRegs& conv, BasicType& ret_type) {
assert(entry->is_static(), "");
// Fill in the signature array, for the calling-convention call.
const int total_out_args = entry->size_of_parameters();
assert(total_out_args > 0, "receiver arg ");
BasicType* out_sig_bt = NEW_RESOURCE_ARRAY(BasicType, total_out_args);
VMRegPair* out_regs = NEW_RESOURCE_ARRAY(VMRegPair, total_out_args);
{
int i = 0;
SignatureStream ss(entry->signature());
for (; !ss.at_return_type(); ss.next()) {
out_sig_bt[i++] = ss.type(); // Collect remaining bits of signature
if (ss.type() == T_LONG || ss.type() == T_DOUBLE)
out_sig_bt[i++] = T_VOID; // Longs & doubles take 2 Java slots
}
assert(i == total_out_args, "");
ret_type = ss.type();
}
int out_arg_slots = SharedRuntime::java_calling_convention(out_sig_bt, out_regs, total_out_args);
const int total_in_args = total_out_args - 1; // skip receiver
BasicType* in_sig_bt = NEW_RESOURCE_ARRAY(BasicType, total_in_args);
VMRegPair* in_regs = NEW_RESOURCE_ARRAY(VMRegPair, total_in_args);
for (int i = 0; i < total_in_args ; i++ ) {
in_sig_bt[i] = out_sig_bt[i+1]; // skip receiver
}
// Now figure out where the args must be stored and how much stack space they require.
conv.calling_convention(in_sig_bt, in_regs, total_in_args);
GrowableArray<int> arg_order(2 * total_in_args);
VMRegPair tmp_vmreg;
tmp_vmreg.set2(rbx->as_VMReg());
// Compute a valid move order, using tmp_vmreg to break any cycles
SharedRuntime::compute_move_order(in_sig_bt,
total_in_args, in_regs,
total_out_args, out_regs,
arg_order,
tmp_vmreg);
GrowableArray<ArgMove> arg_order_vmreg(total_in_args); // conservative
#ifdef ASSERT
bool reg_destroyed[RegisterImpl::number_of_registers];
bool freg_destroyed[XMMRegisterImpl::number_of_registers];
for ( int r = 0 ; r < RegisterImpl::number_of_registers ; r++ ) {
reg_destroyed[r] = false;
}
for ( int f = 0 ; f < XMMRegisterImpl::number_of_registers ; f++ ) {
freg_destroyed[f] = false;
}
#endif // ASSERT
for (int i = 0; i < arg_order.length(); i += 2) {
int in_arg = arg_order.at(i);
int out_arg = arg_order.at(i + 1);
assert(in_arg != -1 || out_arg != -1, "");
BasicType arg_bt = (in_arg != -1 ? in_sig_bt[in_arg] : out_sig_bt[out_arg]);
switch (arg_bt) {
case T_BOOLEAN:
case T_BYTE:
case T_SHORT:
case T_CHAR:
case T_INT:
case T_FLOAT:
break; // process
case T_LONG:
case T_DOUBLE:
assert(in_arg == -1 || (in_arg + 1 < total_in_args && in_sig_bt[in_arg + 1] == T_VOID), "bad arg list: %d", in_arg);
assert(out_arg == -1 || (out_arg + 1 < total_out_args && out_sig_bt[out_arg + 1] == T_VOID), "bad arg list: %d", out_arg);
break; // process
case T_VOID:
continue; // skip
default:
fatal("found in upcall args: %s", type2name(arg_bt));
}
ArgMove move;
move.bt = arg_bt;
move.from = (in_arg != -1 ? in_regs[in_arg] : tmp_vmreg);
move.to = (out_arg != -1 ? out_regs[out_arg] : tmp_vmreg);
if(move.is_identity()) {
continue; // useless move
}
#ifdef ASSERT
if (in_arg != -1) {
if (in_regs[in_arg].first()->is_Register()) {
assert(!reg_destroyed[in_regs[in_arg].first()->as_Register()->encoding()], "destroyed reg!");
} else if (in_regs[in_arg].first()->is_XMMRegister()) {
assert(!freg_destroyed[in_regs[in_arg].first()->as_XMMRegister()->encoding()], "destroyed reg!");
}
}
if (out_arg != -1) {
if (out_regs[out_arg].first()->is_Register()) {
reg_destroyed[out_regs[out_arg].first()->as_Register()->encoding()] = true;
} else if (out_regs[out_arg].first()->is_XMMRegister()) {
freg_destroyed[out_regs[out_arg].first()->as_XMMRegister()->encoding()] = true;
}
}
#endif /* ASSERT */
arg_order_vmreg.push(move);
}
int stack_slots = SharedRuntime::out_preserve_stack_slots() + out_arg_slots;
out_arg_size_bytes = align_up(stack_slots * VMRegImpl::stack_slot_size, StackAlignmentInBytes);
return arg_order_vmreg;
}
static const char* null_safe_string(const char* str) {
return str == nullptr ? "NULL" : str;
}
#ifdef ASSERT
static void print_arg_moves(const GrowableArray<ArgMove>& arg_moves, Method* entry) {
LogTarget(Trace, foreign) lt;
if (lt.is_enabled()) {
ResourceMark rm;
LogStream ls(lt);
ls.print_cr("Argument shuffle for %s {", entry->name_and_sig_as_C_string());
for (int i = 0; i < arg_moves.length(); i++) {
ArgMove arg_mv = arg_moves.at(i);
BasicType arg_bt = arg_mv.bt;
VMRegPair from_vmreg = arg_mv.from;
VMRegPair to_vmreg = arg_mv.to;
ls.print("Move a %s from (", null_safe_string(type2name(arg_bt)));
from_vmreg.first()->print_on(&ls);
ls.print(",");
from_vmreg.second()->print_on(&ls);
ls.print(") to ");
to_vmreg.first()->print_on(&ls);
ls.print(",");
to_vmreg.second()->print_on(&ls);
ls.print_cr(")");
}
ls.print_cr("}");
}
}
#endif
void save_java_frame_anchor(MacroAssembler* _masm, ByteSize store_offset, Register thread) {
__ block_comment("{ save_java_frame_anchor ");
// upcall->jfa._last_Java_fp = _thread->_anchor._last_Java_fp;
__ movptr(rscratch1, Address(thread, JavaThread::last_Java_fp_offset()));
__ movptr(Address(rsp, store_offset + JavaFrameAnchor::last_Java_fp_offset()), rscratch1);
// upcall->jfa._last_Java_pc = _thread->_anchor._last_Java_pc;
__ movptr(rscratch1, Address(thread, JavaThread::last_Java_pc_offset()));
__ movptr(Address(rsp, store_offset + JavaFrameAnchor::last_Java_pc_offset()), rscratch1);
// upcall->jfa._last_Java_sp = _thread->_anchor._last_Java_sp;
__ movptr(rscratch1, Address(thread, JavaThread::last_Java_sp_offset()));
__ movptr(Address(rsp, store_offset + JavaFrameAnchor::last_Java_sp_offset()), rscratch1);
__ block_comment("} save_java_frame_anchor ");
}
void restore_java_frame_anchor(MacroAssembler* _masm, ByteSize load_offset, Register thread) {
__ block_comment("{ restore_java_frame_anchor ");
// thread->_last_Java_sp = NULL
__ movptr(Address(thread, JavaThread::last_Java_sp_offset()), NULL_WORD);
// ThreadStateTransition::transition_from_java(_thread, _thread_in_vm);
// __ movl(Address(r15_thread, JavaThread::thread_state_offset()), _thread_in_native_trans);
__ movl(Address(r15_thread, JavaThread::thread_state_offset()), _thread_in_native);
//_thread->frame_anchor()->copy(&_anchor);
// _thread->_last_Java_fp = upcall->_last_Java_fp;
// _thread->_last_Java_pc = upcall->_last_Java_pc;
// _thread->_last_Java_sp = upcall->_last_Java_sp;
__ movptr(rscratch1, Address(rsp, load_offset + JavaFrameAnchor::last_Java_fp_offset()));
__ movptr(Address(thread, JavaThread::last_Java_fp_offset()), rscratch1);
__ movptr(rscratch1, Address(rsp, load_offset + JavaFrameAnchor::last_Java_pc_offset()));
__ movptr(Address(thread, JavaThread::last_Java_pc_offset()), rscratch1);
__ movptr(rscratch1, Address(rsp, load_offset + JavaFrameAnchor::last_Java_sp_offset()));
__ movptr(Address(thread, JavaThread::last_Java_sp_offset()), rscratch1);
__ block_comment("} restore_java_frame_anchor ");
}
static void save_native_arguments(MacroAssembler* _masm, const CallRegs& conv, int arg_save_area_offset) {
__ block_comment("{ save_native_args ");
int store_offset = arg_save_area_offset;
for (int i = 0; i < conv._args_length; i++) {
VMReg reg = conv._arg_regs[i];
if (reg->is_Register()) {
__ movptr(Address(rsp, store_offset), reg->as_Register());
store_offset += 8;
} else if (reg->is_XMMRegister()) {
// Java API doesn't support vector args
__ movdqu(Address(rsp, store_offset), reg->as_XMMRegister());
store_offset += 16;
}
// do nothing for stack
}
__ block_comment("} save_native_args ");
}
static void restore_native_arguments(MacroAssembler* _masm, const CallRegs& conv, int arg_save_area_offset) {
__ block_comment("{ restore_native_args ");
int load_offset = arg_save_area_offset;
for (int i = 0; i < conv._args_length; i++) {
VMReg reg = conv._arg_regs[i];
if (reg->is_Register()) {
__ movptr(reg->as_Register(), Address(rsp, load_offset));
load_offset += 8;
} else if (reg->is_XMMRegister()) {
// Java API doesn't support vector args
__ movdqu(reg->as_XMMRegister(), Address(rsp, load_offset));
load_offset += 16;
}
// do nothing for stack
}
__ block_comment("} restore_native_args ");
}
static bool is_valid_XMM(XMMRegister reg) {
return reg->is_valid() && (UseAVX >= 3 || (reg->encoding() < 16)); // why is this not covered by is_valid()?
}
// for callee saved regs, according to the caller's ABI
static int compute_reg_save_area_size(const ABIDescriptor& abi) {
int size = 0;
for (Register reg = as_Register(0); reg->is_valid(); reg = reg->successor()) {
if (reg == rbp || reg == rsp) continue; // saved/restored by prologue/epilogue
if (!abi.is_volatile_reg(reg)) {
size += 8; // bytes
}
}
for (XMMRegister reg = as_XMMRegister(0); is_valid_XMM(reg); reg = reg->successor()) {
if (!abi.is_volatile_reg(reg)) {
if (UseAVX >= 3) {
size += 64; // bytes
} else if (UseAVX >= 1) {
size += 32;
} else {
size += 16;
}
}
}
#ifndef _WIN64
// for mxcsr
size += 8;
#endif
return size;
}
static int compute_arg_save_area_size(const CallRegs& conv) {
int result_size = 0;
for (int i = 0; i < conv._args_length; i++) {
VMReg reg = conv._arg_regs[i];
if (reg->is_Register()) {
result_size += 8;
} else if (reg->is_XMMRegister()) {
// Java API doesn't support vector args
result_size += 16;
}
// do nothing for stack
}
return result_size;
}
constexpr int MXCSR_MASK = 0xFFC0; // Mask out any pending exceptions
static void preserve_callee_saved_registers(MacroAssembler* _masm, const ABIDescriptor& abi, int reg_save_area_offset) {
// 1. iterate all registers in the architecture
// - check if they are volatile or not for the given abi
// - if NOT, we need to save it here
// 2. save mxcsr on non-windows platforms
int offset = reg_save_area_offset;
__ block_comment("{ preserve_callee_saved_regs ");
for (Register reg = as_Register(0); reg->is_valid(); reg = reg->successor()) {
if (reg == rbp || reg == rsp) continue; // saved/restored by prologue/epilogue
if (!abi.is_volatile_reg(reg)) {
__ movptr(Address(rsp, offset), reg);
offset += 8;
}
}
for (XMMRegister reg = as_XMMRegister(0); is_valid_XMM(reg); reg = reg->successor()) {
if (!abi.is_volatile_reg(reg)) {
if (UseAVX >= 3) {
__ evmovdqul(Address(rsp, offset), reg, Assembler::AVX_512bit);
offset += 64;
} else if (UseAVX >= 1) {
__ vmovdqu(Address(rsp, offset), reg);
offset += 32;
} else {
__ movdqu(Address(rsp, offset), reg);
offset += 16;
}
}
}
#ifndef _WIN64
{
const Address mxcsr_save(rsp, offset);
Label skip_ldmx;
__ stmxcsr(mxcsr_save);
__ movl(rax, mxcsr_save);
__ andl(rax, MXCSR_MASK); // Only check control and mask bits
ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
__ cmp32(rax, mxcsr_std);
__ jcc(Assembler::equal, skip_ldmx);
__ ldmxcsr(mxcsr_std);
__ bind(skip_ldmx);
}
#endif
__ block_comment("} preserve_callee_saved_regs ");
}
static void restore_callee_saved_registers(MacroAssembler* _masm, const ABIDescriptor& abi, int reg_save_area_offset) {
// 1. iterate all registers in the architecture
// - check if they are volatile or not for the given abi
// - if NOT, we need to restore it here
// 2. restore mxcsr on non-windows platforms
int offset = reg_save_area_offset;
__ block_comment("{ restore_callee_saved_regs ");
for (Register reg = as_Register(0); reg->is_valid(); reg = reg->successor()) {
if (reg == rbp || reg == rsp) continue; // saved/restored by prologue/epilogue
if (!abi.is_volatile_reg(reg)) {
__ movptr(reg, Address(rsp, offset));
offset += 8;
}
}
for (XMMRegister reg = as_XMMRegister(0); is_valid_XMM(reg); reg = reg->successor()) {
if (!abi.is_volatile_reg(reg)) {
if (UseAVX >= 3) {
__ evmovdqul(reg, Address(rsp, offset), Assembler::AVX_512bit);
offset += 64;
} else if (UseAVX >= 1) {
__ vmovdqu(reg, Address(rsp, offset));
offset += 32;
} else {
__ movdqu(reg, Address(rsp, offset));
offset += 16;
}
}
}
#ifndef _WIN64
const Address mxcsr_save(rsp, offset);
__ ldmxcsr(mxcsr_save);
#endif
__ block_comment("} restore_callee_saved_regs ");
}
static void shuffle_arguments(MacroAssembler* _masm, const GrowableArray<ArgMove>& arg_moves) {
for (int i = 0; i < arg_moves.length(); i++) {
ArgMove arg_mv = arg_moves.at(i);
BasicType arg_bt = arg_mv.bt;
VMRegPair from_vmreg = arg_mv.from;
VMRegPair to_vmreg = arg_mv.to;
assert(
!((from_vmreg.first()->is_Register() && to_vmreg.first()->is_XMMRegister())
|| (from_vmreg.first()->is_XMMRegister() && to_vmreg.first()->is_Register())),
"move between gp and fp reg not supported");
__ block_comment(err_msg("bt=%s", null_safe_string(type2name(arg_bt))));
switch (arg_bt) {
case T_BOOLEAN:
case T_BYTE:
case T_SHORT:
case T_CHAR:
case T_INT:
SharedRuntime::move32_64(_masm, from_vmreg, to_vmreg);
break;
case T_FLOAT:
SharedRuntime::float_move(_masm, from_vmreg, to_vmreg);
break;
case T_DOUBLE:
SharedRuntime::double_move(_masm, from_vmreg, to_vmreg);
break;
case T_LONG :
SharedRuntime::long_move(_masm, from_vmreg, to_vmreg);
break;
default:
fatal("found in upcall args: %s", type2name(arg_bt));
}
}
}
struct AuxiliarySaves {
JavaFrameAnchor jfa;
uintptr_t thread;
bool should_detach;
};
address ProgrammableUpcallHandler::generate_optimized_upcall_stub(jobject receiver, Method* entry, jobject jabi, jobject jconv) {
ResourceMark rm;
const ABIDescriptor abi = ForeignGlobals::parse_abi_descriptor(jabi);
const CallRegs conv = ForeignGlobals::parse_call_regs(jconv);
assert(conv._rets_length <= 1, "no multi reg returns");
CodeBuffer buffer("upcall_stub_linkToNative", /* code_size = */ 1024, /* locs_size = */ 1024);
int register_size = sizeof(uintptr_t);
int buffer_alignment = xmm_reg_size;
int out_arg_area = -1;
BasicType ret_type;
GrowableArray<ArgMove> arg_moves = compute_argument_shuffle(entry, out_arg_area, conv, ret_type);
assert(out_arg_area != -1, "Should have been set");
DEBUG_ONLY(print_arg_moves(arg_moves, entry);)
// out_arg_area (for stack arguments) doubles as shadow space for native calls.
// make sure it is big enough.
if (out_arg_area < frame::arg_reg_save_area_bytes) {
out_arg_area = frame::arg_reg_save_area_bytes;
}
int reg_save_area_size = compute_reg_save_area_size(abi);
int arg_save_area_size = compute_arg_save_area_size(conv);
// To spill receiver during deopt
int deopt_spill_size = 1 * BytesPerWord;
int shuffle_area_offset = 0;
int deopt_spill_offset = shuffle_area_offset + out_arg_area;
int arg_save_area_offset = deopt_spill_offset + deopt_spill_size;
int reg_save_area_offset = arg_save_area_offset + arg_save_area_size;
int auxiliary_saves_offset = reg_save_area_offset + reg_save_area_size;
int frame_bottom_offset = auxiliary_saves_offset + sizeof(AuxiliarySaves);
ByteSize jfa_offset = in_ByteSize(auxiliary_saves_offset) + byte_offset_of(AuxiliarySaves, jfa);
ByteSize thread_offset = in_ByteSize(auxiliary_saves_offset) + byte_offset_of(AuxiliarySaves, thread);
ByteSize should_detach_offset = in_ByteSize(auxiliary_saves_offset) + byte_offset_of(AuxiliarySaves, should_detach);
int frame_size = frame_bottom_offset;
frame_size = align_up(frame_size, StackAlignmentInBytes);
// Ok The space we have allocated will look like:
//
//
// FP-> | |
// |---------------------| = frame_bottom_offset = frame_size
// | |
// | AuxiliarySaves |
// |---------------------| = auxiliary_saves_offset
// | |
// | reg_save_area |
// |---------------------| = reg_save_are_offset
// | |
// | arg_save_area |
// |---------------------| = arg_save_are_offset
// | |
// | deopt_spill |
// |---------------------| = deopt_spill_offset
// | |
// SP-> | out_arg_area | needs to be at end for shadow space
//
//
//////////////////////////////////////////////////////////////////////////////
MacroAssembler* _masm = new MacroAssembler(&buffer);
Label call_return;
address start = __ pc();
__ enter(); // set up frame
if ((abi._stack_alignment_bytes % 16) != 0) {
// stack alignment of caller is not a multiple of 16
__ andptr(rsp, -StackAlignmentInBytes); // align stack
}
// allocate frame (frame_size is also aligned, so stack is still aligned)
__ subptr(rsp, frame_size);
// we have to always spill args since we need to do a call to get the thread
// (and maybe attach it).
save_native_arguments(_masm, conv, arg_save_area_offset);
preserve_callee_saved_registers(_masm, abi, reg_save_area_offset);
__ block_comment("{ get_thread");
__ vzeroupper();
__ lea(c_rarg0, Address(rsp, should_detach_offset));
// stack already aligned
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, ProgrammableUpcallHandler::maybe_attach_and_get_thread)));
__ movptr(r15_thread, rax);
__ reinit_heapbase();
__ movptr(Address(rsp, thread_offset), r15_thread);
__ block_comment("} get_thread");
// TODO:
// We expect not to be coming from JNI code, but we might be.
// We should figure out what our stance is on supporting that and then maybe add
// some more handling here for:
// - handle blocks
// - check for active exceptions (and emit an error)
__ block_comment("{ safepoint poll");
__ movl(Address(r15_thread, JavaThread::thread_state_offset()), _thread_in_native_trans);
if (os::is_MP()) {
__ membar(Assembler::Membar_mask_bits(
Assembler::LoadLoad | Assembler::StoreLoad |
Assembler::LoadStore | Assembler::StoreStore));
}
// check for safepoint operation in progress and/or pending suspend requests
Label L_after_safepoint_poll;
Label L_safepoint_poll_slow_path;
__ safepoint_poll(L_safepoint_poll_slow_path, r15_thread, false /* at_return */, false /* in_nmethod */);
__ cmpl(Address(r15_thread, JavaThread::suspend_flags_offset()), 0);
__ jcc(Assembler::notEqual, L_safepoint_poll_slow_path);
__ bind(L_after_safepoint_poll);
__ block_comment("} safepoint poll");
// change thread state
__ movl(Address(r15_thread, JavaThread::thread_state_offset()), _thread_in_Java);
__ block_comment("{ reguard stack check");
Label L_reguard;
Label L_after_reguard;
__ cmpl(Address(r15_thread, JavaThread::stack_guard_state_offset()), StackOverflow::stack_guard_yellow_reserved_disabled);
__ jcc(Assembler::equal, L_reguard);
__ bind(L_after_reguard);
__ block_comment("} reguard stack check");
__ block_comment("{ argument shuffle");
// TODO merge these somehow
restore_native_arguments(_masm, conv, arg_save_area_offset);
shuffle_arguments(_masm, arg_moves);
__ block_comment("} argument shuffle");
__ block_comment("{ receiver ");
__ movptr(rscratch1, (intptr_t)receiver);
__ resolve_jobject(rscratch1, r15_thread, rscratch2);
__ movptr(j_rarg0, rscratch1);
__ block_comment("} receiver ");
__ mov_metadata(rbx, entry);
__ movptr(Address(r15_thread, JavaThread::callee_target_offset()), rbx); // just in case callee is deoptimized
__ reinit_heapbase();
save_java_frame_anchor(_masm, jfa_offset, r15_thread);
__ reset_last_Java_frame(r15_thread, true);
__ call(Address(rbx, Method::from_compiled_offset()));
#ifdef ASSERT
if (conv._rets_length == 1) { // 0 or 1
VMReg j_expected_result_reg;
switch (ret_type) {
case T_BOOLEAN:
case T_BYTE:
case T_SHORT:
case T_CHAR:
case T_INT:
case T_LONG:
j_expected_result_reg = rax->as_VMReg();
break;
case T_FLOAT:
case T_DOUBLE:
j_expected_result_reg = xmm0->as_VMReg();
break;
default:
fatal("unexpected return type: %s", type2name(ret_type));
}
// No need to move for now, since CallArranger can pick a return type
// that goes in the same reg for both CCs. But, at least assert they are the same
assert(conv._ret_regs[0] == j_expected_result_reg,
"unexpected result register: %s != %s", conv._ret_regs[0]->name(), j_expected_result_reg->name());
}
#endif
__ bind(call_return);
// also sets last Java frame
__ movptr(r15_thread, Address(rsp, thread_offset));
// TODO corrupted thread pointer causes havoc. Can we verify it here?
restore_java_frame_anchor(_masm, jfa_offset, r15_thread); // also transitions to native state
__ block_comment("{ maybe_detach_thread");
Label L_after_detach;
__ cmpb(Address(rsp, should_detach_offset), 0);
__ jcc(Assembler::equal, L_after_detach);
__ vzeroupper();
__ mov(c_rarg0, r15_thread);
// stack already aligned
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, ProgrammableUpcallHandler::detach_thread)));
__ reinit_heapbase();
__ bind(L_after_detach);
__ block_comment("} maybe_detach_thread");
restore_callee_saved_registers(_masm, abi, reg_save_area_offset);
__ leave();
__ ret(0);
//////////////////////////////////////////////////////////////////////////////
__ block_comment("{ L_safepoint_poll_slow_path");
__ bind(L_safepoint_poll_slow_path);
__ vzeroupper();
__ mov(c_rarg0, r15_thread);
// stack already aligned
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, JavaThread::check_special_condition_for_native_trans)));
__ reinit_heapbase();
__ jmp(L_after_safepoint_poll);
__ block_comment("} L_safepoint_poll_slow_path");
//////////////////////////////////////////////////////////////////////////////
__ block_comment("{ L_reguard");
__ bind(L_reguard);
__ vzeroupper();
// stack already aligned
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, SharedRuntime::reguard_yellow_pages)));
__ reinit_heapbase();
__ jmp(L_after_reguard);
__ block_comment("} L_reguard");
//////////////////////////////////////////////////////////////////////////////
__ block_comment("{ exception handler");
intptr_t exception_handler_offset = __ pc() - start;
// TODO: this is always the same, can we bypass and call handle_uncaught_exception directly?
// native caller has no idea how to handle exceptions
// we just crash here. Up to callee to catch exceptions.
__ verify_oop(rax);
__ vzeroupper();
__ mov(c_rarg0, rax);
__ andptr(rsp, -StackAlignmentInBytes); // align stack as required by ABI
__ subptr(rsp, frame::arg_reg_save_area_bytes); // windows (not really needed)
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, ProgrammableUpcallHandler::handle_uncaught_exception)));
__ should_not_reach_here();
__ block_comment("} exception handler");
_masm->flush();
#ifndef PRODUCT
stringStream ss;
ss.print("optimized_upcall_stub_%s", entry->signature()->as_C_string());
const char* name = _masm->code_string(ss.as_string());
#else // PRODUCT
const char* name = "optimized_upcall_stub";
#endif // PRODUCT
OptimizedEntryBlob* blob = OptimizedEntryBlob::create(name, &buffer, exception_handler_offset, receiver, jfa_offset);
if (TraceOptimizedUpcallStubs) {
blob->print_on(tty);
Disassembler::decode(blob, tty);
}
return blob->code_begin();
}
bool ProgrammableUpcallHandler::supports_optimized_upcalls() {
return true;
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -60,11 +60,6 @@ ciNativeEntryPoint::ciNativeEntryPoint(instanceHandle h_i) : ciInstance(h_i), _n
_ret_moves = getVMRegArray(CURRENT_ENV->get_object(jdk_internal_invoke_NativeEntryPoint::returnMoves(get_oop()))->as_array()); _ret_moves = getVMRegArray(CURRENT_ENV->get_object(jdk_internal_invoke_NativeEntryPoint::returnMoves(get_oop()))->as_array());
} }
address ciNativeEntryPoint::entry_point() const {
VM_ENTRY_MARK;
return jdk_internal_invoke_NativeEntryPoint::addr(get_oop());
}
jint ciNativeEntryPoint::shadow_space() const { jint ciNativeEntryPoint::shadow_space() const {
VM_ENTRY_MARK; VM_ENTRY_MARK;
return jdk_internal_invoke_NativeEntryPoint::shadow_space(get_oop()); return jdk_internal_invoke_NativeEntryPoint::shadow_space(get_oop());

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -44,7 +44,6 @@ public:
// What kind of ciObject is this? // What kind of ciObject is this?
bool is_native_entry_point() const { return true; } bool is_native_entry_point() const { return true; }
address entry_point() const;
jint shadow_space() const; jint shadow_space() const;
VMReg* argMoves() const; VMReg* argMoves() const;
VMReg* returnMoves() const; VMReg* returnMoves() const;

View file

@ -3890,7 +3890,6 @@ bool java_lang_invoke_LambdaForm::is_instance(oop obj) {
return obj != NULL && is_subclass(obj->klass()); return obj != NULL && is_subclass(obj->klass());
} }
int jdk_internal_invoke_NativeEntryPoint::_addr_offset;
int jdk_internal_invoke_NativeEntryPoint::_shadow_space_offset; int jdk_internal_invoke_NativeEntryPoint::_shadow_space_offset;
int jdk_internal_invoke_NativeEntryPoint::_argMoves_offset; int jdk_internal_invoke_NativeEntryPoint::_argMoves_offset;
int jdk_internal_invoke_NativeEntryPoint::_returnMoves_offset; int jdk_internal_invoke_NativeEntryPoint::_returnMoves_offset;
@ -3899,7 +3898,6 @@ int jdk_internal_invoke_NativeEntryPoint::_method_type_offset;
int jdk_internal_invoke_NativeEntryPoint::_name_offset; int jdk_internal_invoke_NativeEntryPoint::_name_offset;
#define NEP_FIELDS_DO(macro) \ #define NEP_FIELDS_DO(macro) \
macro(_addr_offset, k, "addr", long_signature, false); \
macro(_shadow_space_offset, k, "shadowSpace", int_signature, false); \ macro(_shadow_space_offset, k, "shadowSpace", int_signature, false); \
macro(_argMoves_offset, k, "argMoves", long_array_signature, false); \ macro(_argMoves_offset, k, "argMoves", long_array_signature, false); \
macro(_returnMoves_offset, k, "returnMoves", long_array_signature, false); \ macro(_returnMoves_offset, k, "returnMoves", long_array_signature, false); \
@ -3922,10 +3920,6 @@ void jdk_internal_invoke_NativeEntryPoint::serialize_offsets(SerializeClosure* f
} }
#endif #endif
address jdk_internal_invoke_NativeEntryPoint::addr(oop entry) {
return (address)entry->long_field(_addr_offset);
}
jint jdk_internal_invoke_NativeEntryPoint::shadow_space(oop entry) { jint jdk_internal_invoke_NativeEntryPoint::shadow_space(oop entry) {
return entry->int_field(_shadow_space_offset); return entry->int_field(_shadow_space_offset);
} }

View file

@ -1043,7 +1043,6 @@ class jdk_internal_invoke_NativeEntryPoint: AllStatic {
friend class JavaClasses; friend class JavaClasses;
private: private:
static int _addr_offset; // type is jlong
static int _shadow_space_offset; static int _shadow_space_offset;
static int _argMoves_offset; static int _argMoves_offset;
static int _returnMoves_offset; static int _returnMoves_offset;
@ -1057,7 +1056,6 @@ class jdk_internal_invoke_NativeEntryPoint: AllStatic {
static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN; static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN;
// Accessors // Accessors
static address addr(oop entry);
static jint shadow_space(oop entry); static jint shadow_space(oop entry);
static oop argMoves(oop entry); static oop argMoves(oop entry);
static oop returnMoves(oop entry); static oop returnMoves(oop entry);
@ -1073,7 +1071,6 @@ class jdk_internal_invoke_NativeEntryPoint: AllStatic {
static bool is_instance(oop obj); static bool is_instance(oop obj);
// Accessors for code generation: // Accessors for code generation:
static int addr_offset_in_bytes() { return _addr_offset; }
static int shadow_space_offset_in_bytes() { return _shadow_space_offset; } static int shadow_space_offset_in_bytes() { return _shadow_space_offset; }
static int argMoves_offset_in_bytes() { return _argMoves_offset; } static int argMoves_offset_in_bytes() { return _argMoves_offset; }
static int returnMoves_offset_in_bytes() { return _returnMoves_offset; } static int returnMoves_offset_in_bytes() { return _returnMoves_offset; }

View file

@ -346,7 +346,7 @@
template(DEFAULT_CONTEXT_name, "DEFAULT_CONTEXT") \ template(DEFAULT_CONTEXT_name, "DEFAULT_CONTEXT") \
NOT_LP64( do_alias(intptr_signature, int_signature) ) \ NOT_LP64( do_alias(intptr_signature, int_signature) ) \
LP64_ONLY( do_alias(intptr_signature, long_signature) ) \ LP64_ONLY( do_alias(intptr_signature, long_signature) ) \
/* Panama Support */ \ /* Foreign API Support */ \
template(jdk_internal_invoke_NativeEntryPoint, "jdk/internal/invoke/NativeEntryPoint") \ template(jdk_internal_invoke_NativeEntryPoint, "jdk/internal/invoke/NativeEntryPoint") \
template(jdk_internal_invoke_NativeEntryPoint_signature, "Ljdk/internal/invoke/NativeEntryPoint;") \ template(jdk_internal_invoke_NativeEntryPoint_signature, "Ljdk/internal/invoke/NativeEntryPoint;") \
template(jdk_incubator_foreign_MemoryAccess, "jdk/incubator/foreign/MemoryAccess") \ template(jdk_incubator_foreign_MemoryAccess, "jdk/incubator/foreign/MemoryAccess") \

View file

@ -709,3 +709,30 @@ void SingletonBlob::print_value_on(outputStream* st) const {
void DeoptimizationBlob::print_value_on(outputStream* st) const { void DeoptimizationBlob::print_value_on(outputStream* st) const {
st->print_cr("Deoptimization (frame not available)"); st->print_cr("Deoptimization (frame not available)");
} }
// Implementation of OptimizedEntryBlob
OptimizedEntryBlob::OptimizedEntryBlob(const char* name, int size, CodeBuffer* cb, intptr_t exception_handler_offset,
jobject receiver, ByteSize jfa_sp_offset) :
BufferBlob(name, size, cb),
_exception_handler_offset(exception_handler_offset),
_receiver(receiver),
_jfa_sp_offset(jfa_sp_offset) {
CodeCache::commit(this);
}
OptimizedEntryBlob* OptimizedEntryBlob::create(const char* name, CodeBuffer* cb, intptr_t exception_handler_offset,
jobject receiver, ByteSize jfa_sp_offset) {
ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock
OptimizedEntryBlob* blob = nullptr;
unsigned int size = CodeBlob::allocation_size(cb, sizeof(OptimizedEntryBlob));
{
MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
blob = new (size) OptimizedEntryBlob(name, size, cb, exception_handler_offset, receiver, jfa_sp_offset);
}
// Track memory usage statistic after releasing CodeCache_lock
MemoryService::track_code_cache_memory_usage();
return blob;
}

View file

@ -58,6 +58,7 @@ struct CodeBlobType {
// AdapterBlob : Used to hold C2I/I2C adapters // AdapterBlob : Used to hold C2I/I2C adapters
// VtableBlob : Used for holding vtable chunks // VtableBlob : Used for holding vtable chunks
// MethodHandlesAdapterBlob : Used to hold MethodHandles adapters // MethodHandlesAdapterBlob : Used to hold MethodHandles adapters
// OptimizedEntryBlob : Used for upcalls from native code
// RuntimeStub : Call to VM runtime methods // RuntimeStub : Call to VM runtime methods
// SingletonBlob : Super-class for all blobs that exist in only one instance // SingletonBlob : Super-class for all blobs that exist in only one instance
// DeoptimizationBlob : Used for deoptimization // DeoptimizationBlob : Used for deoptimization
@ -75,6 +76,8 @@ struct CodeBlobType {
class CodeBlobLayout; class CodeBlobLayout;
class OptimizedEntryBlob; // for as_optimized_entry_blob()
class JavaFrameAnchor; // for EntryBlob::jfa_for_frame
class CodeBlob { class CodeBlob {
friend class VMStructs; friend class VMStructs;
@ -136,6 +139,7 @@ public:
virtual bool is_vtable_blob() const { return false; } virtual bool is_vtable_blob() const { return false; }
virtual bool is_method_handles_adapter_blob() const { return false; } virtual bool is_method_handles_adapter_blob() const { return false; }
virtual bool is_compiled() const { return false; } virtual bool is_compiled() const { return false; }
virtual bool is_optimized_entry_blob() const { return false; }
inline bool is_compiled_by_c1() const { return _type == compiler_c1; }; inline bool is_compiled_by_c1() const { return _type == compiler_c1; };
inline bool is_compiled_by_c2() const { return _type == compiler_c2; }; inline bool is_compiled_by_c2() const { return _type == compiler_c2; };
@ -149,6 +153,7 @@ public:
CompiledMethod* as_compiled_method_or_null() { return is_compiled() ? (CompiledMethod*) this : NULL; } CompiledMethod* as_compiled_method_or_null() { return is_compiled() ? (CompiledMethod*) this : NULL; }
CompiledMethod* as_compiled_method() { assert(is_compiled(), "must be compiled"); return (CompiledMethod*) this; } CompiledMethod* as_compiled_method() { assert(is_compiled(), "must be compiled"); return (CompiledMethod*) this; }
CodeBlob* as_codeblob_or_null() const { return (CodeBlob*) this; } CodeBlob* as_codeblob_or_null() const { return (CodeBlob*) this; }
OptimizedEntryBlob* as_optimized_entry_blob() const { assert(is_optimized_entry_blob(), "must be entry blob"); return (OptimizedEntryBlob*) this; }
// Boundaries // Boundaries
address header_begin() const { return (address) this; } address header_begin() const { return (address) this; }
@ -379,6 +384,7 @@ class BufferBlob: public RuntimeBlob {
friend class AdapterBlob; friend class AdapterBlob;
friend class VtableBlob; friend class VtableBlob;
friend class MethodHandlesAdapterBlob; friend class MethodHandlesAdapterBlob;
friend class OptimizedEntryBlob;
friend class WhiteBox; friend class WhiteBox;
private: private:
@ -718,4 +724,33 @@ class SafepointBlob: public SingletonBlob {
bool is_safepoint_stub() const { return true; } bool is_safepoint_stub() const { return true; }
}; };
//----------------------------------------------------------------------------------------------------
// For optimized upcall stubs
class OptimizedEntryBlob: public BufferBlob {
private:
intptr_t _exception_handler_offset;
jobject _receiver;
ByteSize _jfa_sp_offset;
OptimizedEntryBlob(const char* name, int size, CodeBuffer* cb, intptr_t exception_handler_offset,
jobject receiver, ByteSize jfa_sp_offset);
public:
// Creation
static OptimizedEntryBlob* create(const char* name, CodeBuffer* cb,
intptr_t exception_handler_offset, jobject receiver,
ByteSize jfa_sp_offset);
address exception_handler() { return code_begin() + _exception_handler_offset; }
jobject receiver() { return _receiver; }
ByteSize jfa_sp_offset() const { return _jfa_sp_offset; }
// defined in frame_ARCH.cpp
JavaFrameAnchor* jfa_for_frame(const frame& frame) const;
// Typing
virtual bool is_optimized_entry_blob() const override { return true; }
};
#endif // SHARE_CODE_CODEBLOB_HPP #endif // SHARE_CODE_CODEBLOB_HPP

View file

@ -73,6 +73,7 @@
LOG_TAG(exceptions) \ LOG_TAG(exceptions) \
LOG_TAG(exit) \ LOG_TAG(exit) \
LOG_TAG(fingerprint) \ LOG_TAG(fingerprint) \
DEBUG_ONLY(LOG_TAG(foreign)) \
LOG_TAG(free) \ LOG_TAG(free) \
LOG_TAG(freelist) \ LOG_TAG(freelist) \
LOG_TAG(gc) \ LOG_TAG(gc) \

View file

@ -418,8 +418,9 @@ class LateInlineMHCallGenerator : public LateInlineCallGenerator {
bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) { bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) {
// Even if inlining is not allowed, a virtual call can be strength-reduced to a direct call. // Even if inlining is not allowed, a virtual call can be strength-reduced to a direct call.
bool allow_inline = C->inlining_incrementally(); bool allow_inline = C->inlining_incrementally();
CallGenerator* cg = for_method_handle_inline(jvms, _caller, method(), allow_inline, _input_not_const); bool input_not_const = true;
assert(!_input_not_const, "sanity"); // shouldn't have been scheduled for inlining in the first place CallGenerator* cg = for_method_handle_inline(jvms, _caller, method(), allow_inline, input_not_const);
assert(!input_not_const, "sanity"); // shouldn't have been scheduled for inlining in the first place
if (cg != NULL) { if (cg != NULL) {
assert(!cg->is_late_inline() || cg->is_mh_late_inline() || AlwaysIncrementalInline, "we're doing late inlining"); assert(!cg->is_late_inline() || cg->is_mh_late_inline() || AlwaysIncrementalInline, "we're doing late inlining");
@ -1054,10 +1055,11 @@ CallGenerator* CallGenerator::for_method_handle_call(JVMState* jvms, ciMethod* c
class NativeCallGenerator : public CallGenerator { class NativeCallGenerator : public CallGenerator {
private: private:
address _call_addr;
ciNativeEntryPoint* _nep; ciNativeEntryPoint* _nep;
public: public:
NativeCallGenerator(ciMethod* m, ciNativeEntryPoint* nep) NativeCallGenerator(ciMethod* m, address call_addr, ciNativeEntryPoint* nep)
: CallGenerator(m), _nep(nep) {} : CallGenerator(m), _call_addr(call_addr), _nep(nep) {}
virtual JVMState* generate(JVMState* jvms); virtual JVMState* generate(JVMState* jvms);
}; };
@ -1065,13 +1067,12 @@ public:
JVMState* NativeCallGenerator::generate(JVMState* jvms) { JVMState* NativeCallGenerator::generate(JVMState* jvms) {
GraphKit kit(jvms); GraphKit kit(jvms);
Node* call = kit.make_native_call(tf(), method()->arg_size(), _nep); // -fallback, - nep Node* call = kit.make_native_call(_call_addr, tf(), method()->arg_size(), _nep); // -fallback, - nep
if (call == NULL) return NULL; if (call == NULL) return NULL;
kit.C->print_inlining_update(this); kit.C->print_inlining_update(this);
address addr = _nep->entry_point();
if (kit.C->log() != NULL) { if (kit.C->log() != NULL) {
kit.C->log()->elem("l2n_intrinsification_success bci='%d' entry_point='" INTPTR_FORMAT "'", jvms->bci(), p2i(addr)); kit.C->log()->elem("l2n_intrinsification_success bci='%d' entry_point='" INTPTR_FORMAT "'", jvms->bci(), p2i(_call_addr));
} }
return kit.transfer_exceptions_into_jvms(); return kit.transfer_exceptions_into_jvms();
@ -1204,12 +1205,16 @@ CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod*
case vmIntrinsics::_linkToNative: case vmIntrinsics::_linkToNative:
{ {
Node* nep = kit.argument(callee->arg_size() - 1); Node* addr_n = kit.argument(1); // target address
if (nep->Opcode() == Op_ConP) { Node* nep_n = kit.argument(callee->arg_size() - 1); // NativeEntryPoint
// This check needs to be kept in sync with the one in CallStaticJavaNode::Ideal
if (addr_n->Opcode() == Op_ConL && nep_n->Opcode() == Op_ConP) {
input_not_const = false; input_not_const = false;
const TypeOopPtr* oop_ptr = nep->bottom_type()->is_oopptr(); const TypeLong* addr_t = addr_n->bottom_type()->is_long();
ciNativeEntryPoint* nep = oop_ptr->const_oop()->as_native_entry_point(); const TypeOopPtr* nep_t = nep_n->bottom_type()->is_oopptr();
return new NativeCallGenerator(callee, nep); address addr = (address) addr_t->get_con();
ciNativeEntryPoint* nep = nep_t->const_oop()->as_native_entry_point();
return new NativeCallGenerator(callee, addr, nep);
} else { } else {
print_inlining_failure(C, callee, jvms->depth() - 1, jvms->bci(), print_inlining_failure(C, callee, jvms->depth() - 1, jvms->bci(),
"NativeEntryPoint not constant"); "NativeEntryPoint not constant");

View file

@ -1066,6 +1066,12 @@ Node* CallStaticJavaNode::Ideal(PhaseGVN* phase, bool can_reshape) {
phase->C->prepend_late_inline(cg); phase->C->prepend_late_inline(cg);
set_generator(NULL); set_generator(NULL);
} }
} else if (iid == vmIntrinsics::_linkToNative) {
if (in(TypeFunc::Parms + callee->arg_size() - 1)->Opcode() == Op_ConP /* NEP */
&& in(TypeFunc::Parms + 1)->Opcode() == Op_ConL /* address */) {
phase->C->prepend_late_inline(cg);
set_generator(NULL);
}
} else { } else {
assert(callee->has_member_arg(), "wrong type of call?"); assert(callee->has_member_arg(), "wrong type of call?");
if (in(TypeFunc::Parms + callee->arg_size() - 1)->Opcode() == Op_ConP) { if (in(TypeFunc::Parms + callee->arg_size() - 1)->Opcode() == Op_ConP) {

View file

@ -2566,8 +2566,13 @@ Node* GraphKit::sign_extend_short(Node* in) {
} }
//-----------------------------make_native_call------------------------------- //-----------------------------make_native_call-------------------------------
Node* GraphKit::make_native_call(const TypeFunc* call_type, uint nargs, ciNativeEntryPoint* nep) { Node* GraphKit::make_native_call(address call_addr, const TypeFunc* call_type, uint nargs, ciNativeEntryPoint* nep) {
uint n_filtered_args = nargs - 2; // -fallback, -nep; // Select just the actual call args to pass on
// [MethodHandle fallback, long addr, HALF addr, ... args , NativeEntryPoint nep]
// | |
// V V
// [ ... args ]
uint n_filtered_args = nargs - 4; // -fallback, -addr (2), -nep;
ResourceMark rm; ResourceMark rm;
Node** argument_nodes = NEW_RESOURCE_ARRAY(Node*, n_filtered_args); Node** argument_nodes = NEW_RESOURCE_ARRAY(Node*, n_filtered_args);
const Type** arg_types = TypeTuple::fields(n_filtered_args); const Type** arg_types = TypeTuple::fields(n_filtered_args);
@ -2577,7 +2582,7 @@ Node* GraphKit::make_native_call(const TypeFunc* call_type, uint nargs, ciNative
{ {
for (uint vm_arg_pos = 0, java_arg_read_pos = 0; for (uint vm_arg_pos = 0, java_arg_read_pos = 0;
vm_arg_pos < n_filtered_args; vm_arg_pos++) { vm_arg_pos < n_filtered_args; vm_arg_pos++) {
uint vm_unfiltered_arg_pos = vm_arg_pos + 1; // +1 to skip fallback handle argument uint vm_unfiltered_arg_pos = vm_arg_pos + 3; // +3 to skip fallback handle argument and addr (2 since long)
Node* node = argument(vm_unfiltered_arg_pos); Node* node = argument(vm_unfiltered_arg_pos);
const Type* type = call_type->domain()->field_at(TypeFunc::Parms + vm_unfiltered_arg_pos); const Type* type = call_type->domain()->field_at(TypeFunc::Parms + vm_unfiltered_arg_pos);
VMReg reg = type == Type::HALF VMReg reg = type == Type::HALF
@ -2613,7 +2618,6 @@ Node* GraphKit::make_native_call(const TypeFunc* call_type, uint nargs, ciNative
TypeTuple::make(TypeFunc::Parms + n_returns, ret_types) TypeTuple::make(TypeFunc::Parms + n_returns, ret_types)
); );
address call_addr = nep->entry_point();
if (nep->need_transition()) { if (nep->need_transition()) {
RuntimeStub* invoker = SharedRuntime::make_native_invoker(call_addr, RuntimeStub* invoker = SharedRuntime::make_native_invoker(call_addr,
nep->shadow_space(), nep->shadow_space(),

View file

@ -801,7 +801,7 @@ class GraphKit : public Phase {
Node* sign_extend_byte(Node* in); Node* sign_extend_byte(Node* in);
Node* sign_extend_short(Node* in); Node* sign_extend_short(Node* in);
Node* make_native_call(const TypeFunc* call_type, uint nargs, ciNativeEntryPoint* nep); Node* make_native_call(address call_addr, const TypeFunc* call_type, uint nargs, ciNativeEntryPoint* nep);
enum { // flag values for make_runtime_call enum { // flag values for make_runtime_call
RC_NO_FP = 1, // CallLeafNoFPNode RC_NO_FP = 1, // CallLeafNoFPNode

View file

@ -880,7 +880,7 @@ uint PhaseCFG::sched_call(Block* block, uint node_cnt, Node_List& worklist, Grow
save_policy = _matcher._register_save_policy; save_policy = _matcher._register_save_policy;
break; break;
case Op_CallNative: case Op_CallNative:
// We use the c reg save policy here since Panama // We use the c reg save policy here since Foreign Linker
// only supports the C ABI currently. // only supports the C ABI currently.
// TODO compute actual save policy based on nep->abi // TODO compute actual save policy based on nep->abi
save_policy = _matcher._c_reg_save_policy; save_policy = _matcher._c_reg_save_policy;

View file

@ -59,23 +59,28 @@ const BufferLayout ForeignGlobals::parse_buffer_layout(jobject jlayout) {
return instance().parse_buffer_layout_impl(jlayout); return instance().parse_buffer_layout_impl(jlayout);
} }
const CallRegs ForeignGlobals::parse_call_regs(jobject jconv) {
return instance().parse_call_regs_impl(jconv);
}
ForeignGlobals::ForeignGlobals() { ForeignGlobals::ForeignGlobals() {
JavaThread* current_thread = JavaThread::current(); JavaThread* current_thread = JavaThread::current();
ResourceMark rm(current_thread); ResourceMark rm(current_thread);
// ABIDescriptor // ABIDescriptor
InstanceKlass* k_ABI = find_InstanceKlass(FOREIGN_ABI "ABIDescriptor", current_thread); InstanceKlass* k_ABI = find_InstanceKlass(FOREIGN_ABI "ABIDescriptor", current_thread);
const char* strVMSArray = "[[L" FOREIGN_ABI "VMStorage;"; const char* strVMSArrayArray = "[[L" FOREIGN_ABI "VMStorage;";
Symbol* symVMSArray = SymbolTable::new_symbol(strVMSArray, (int)strlen(strVMSArray)); Symbol* symVMSArrayArray = SymbolTable::new_symbol(strVMSArrayArray);
ABI.inputStorage_offset = field_offset(k_ABI, "inputStorage", symVMSArray); ABI.inputStorage_offset = field_offset(k_ABI, "inputStorage", symVMSArrayArray);
ABI.outputStorage_offset = field_offset(k_ABI, "outputStorage", symVMSArray); ABI.outputStorage_offset = field_offset(k_ABI, "outputStorage", symVMSArrayArray);
ABI.volatileStorage_offset = field_offset(k_ABI, "volatileStorage", symVMSArray); ABI.volatileStorage_offset = field_offset(k_ABI, "volatileStorage", symVMSArrayArray);
ABI.stackAlignment_offset = field_offset(k_ABI, "stackAlignment", vmSymbols::int_signature()); ABI.stackAlignment_offset = field_offset(k_ABI, "stackAlignment", vmSymbols::int_signature());
ABI.shadowSpace_offset = field_offset(k_ABI, "shadowSpace", vmSymbols::int_signature()); ABI.shadowSpace_offset = field_offset(k_ABI, "shadowSpace", vmSymbols::int_signature());
// VMStorage // VMStorage
InstanceKlass* k_VMS = find_InstanceKlass(FOREIGN_ABI "VMStorage", current_thread); InstanceKlass* k_VMS = find_InstanceKlass(FOREIGN_ABI "VMStorage", current_thread);
VMS.index_offset = field_offset(k_VMS, "index", vmSymbols::int_signature()); VMS.index_offset = field_offset(k_VMS, "index", vmSymbols::int_signature());
VMS.type_offset = field_offset(k_VMS, "type", vmSymbols::int_signature());
// BufferLayout // BufferLayout
InstanceKlass* k_BL = find_InstanceKlass(FOREIGN_ABI "BufferLayout", current_thread); InstanceKlass* k_BL = find_InstanceKlass(FOREIGN_ABI "BufferLayout", current_thread);
@ -85,4 +90,41 @@ ForeignGlobals::ForeignGlobals() {
BL.stack_args_offset = field_offset(k_BL, "stack_args", vmSymbols::long_signature()); BL.stack_args_offset = field_offset(k_BL, "stack_args", vmSymbols::long_signature());
BL.input_type_offsets_offset = field_offset(k_BL, "input_type_offsets", vmSymbols::long_array_signature()); BL.input_type_offsets_offset = field_offset(k_BL, "input_type_offsets", vmSymbols::long_array_signature());
BL.output_type_offsets_offset = field_offset(k_BL, "output_type_offsets", vmSymbols::long_array_signature()); BL.output_type_offsets_offset = field_offset(k_BL, "output_type_offsets", vmSymbols::long_array_signature());
// CallRegs
const char* strVMSArray = "[L" FOREIGN_ABI "VMStorage;";
Symbol* symVMSArray = SymbolTable::new_symbol(strVMSArray);
InstanceKlass* k_CC = find_InstanceKlass(FOREIGN_ABI "ProgrammableUpcallHandler$CallRegs", current_thread);
CallConvOffsets.arg_regs_offset = field_offset(k_CC, "argRegs", symVMSArray);
CallConvOffsets.ret_regs_offset = field_offset(k_CC, "retRegs", symVMSArray);
}
void CallRegs::calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint argcnt) const {
int src_pos = 0;
for (uint i = 0; i < argcnt; i++) {
switch (sig_bt[i]) {
case T_BOOLEAN:
case T_CHAR:
case T_BYTE:
case T_SHORT:
case T_INT:
case T_FLOAT:
assert(src_pos < _args_length, "oob");
parm_regs[i].set1(_arg_regs[src_pos++]);
break;
case T_LONG:
case T_DOUBLE:
assert((i + 1) < argcnt && sig_bt[i + 1] == T_VOID, "expecting half");
assert(src_pos < _args_length, "oob");
parm_regs[i].set2(_arg_regs[src_pos++]);
break;
case T_VOID: // Halves of longs and doubles
assert(i != 0 && (sig_bt[i - 1] == T_LONG || sig_bt[i - 1] == T_DOUBLE), "expecting half");
parm_regs[i].set_bad();
break;
default:
ShouldNotReachHere();
break;
}
}
} }

View file

@ -24,12 +24,23 @@
#ifndef SHARE_PRIMS_FOREIGN_GLOBALS #ifndef SHARE_PRIMS_FOREIGN_GLOBALS
#define SHARE_PRIMS_FOREIGN_GLOBALS #define SHARE_PRIMS_FOREIGN_GLOBALS
#include "code/vmreg.hpp"
#include "oops/oopsHierarchy.hpp" #include "oops/oopsHierarchy.hpp"
#include "utilities/growableArray.hpp" #include "utilities/growableArray.hpp"
#include "utilities/macros.hpp" #include "utilities/macros.hpp"
#include CPU_HEADER(foreign_globals) #include CPU_HEADER(foreign_globals)
struct CallRegs {
VMReg* _arg_regs;
int _args_length;
VMReg* _ret_regs;
int _rets_length;
void calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint argcnt) const;
};
class ForeignGlobals { class ForeignGlobals {
private: private:
struct { struct {
@ -42,6 +53,7 @@ private:
struct { struct {
int index_offset; int index_offset;
int type_offset;
} VMS; } VMS;
struct { struct {
@ -53,6 +65,11 @@ private:
int output_type_offsets_offset; int output_type_offsets_offset;
} BL; } BL;
struct {
int arg_regs_offset;
int ret_regs_offset;
} CallConvOffsets;
ForeignGlobals(); ForeignGlobals();
static const ForeignGlobals& instance(); static const ForeignGlobals& instance();
@ -65,9 +82,11 @@ private:
const ABIDescriptor parse_abi_descriptor_impl(jobject jabi) const; const ABIDescriptor parse_abi_descriptor_impl(jobject jabi) const;
const BufferLayout parse_buffer_layout_impl(jobject jlayout) const; const BufferLayout parse_buffer_layout_impl(jobject jlayout) const;
const CallRegs parse_call_regs_impl(jobject jconv) const;
public: public:
static const ABIDescriptor parse_abi_descriptor(jobject jabi); static const ABIDescriptor parse_abi_descriptor(jobject jabi);
static const BufferLayout parse_buffer_layout(jobject jlayout); static const BufferLayout parse_buffer_layout(jobject jlayout);
static const CallRegs parse_call_regs(jobject jconv);
}; };
#endif // SHARE_PRIMS_FOREIGN_GLOBALS #endif // SHARE_PRIMS_FOREIGN_GLOBALS

View file

@ -37,7 +37,8 @@ static JNINativeMethod NEP_methods[] = {
{CC "vmStorageToVMReg", CC "(II)J", FN_PTR(NEP_vmStorageToVMReg)}, {CC "vmStorageToVMReg", CC "(II)J", FN_PTR(NEP_vmStorageToVMReg)},
}; };
JNI_LEAF(void, JVM_RegisterNativeEntryPointMethods(JNIEnv *env, jclass NEP_class)) JNI_ENTRY(void, JVM_RegisterNativeEntryPointMethods(JNIEnv *env, jclass NEP_class))
ThreadToNativeFromVM ttnfv(thread);
int status = env->RegisterNatives(NEP_class, NEP_methods, sizeof(NEP_methods)/sizeof(JNINativeMethod)); int status = env->RegisterNatives(NEP_class, NEP_methods, sizeof(NEP_methods)/sizeof(JNINativeMethod));
guarantee(status == JNI_OK && !env->ExceptionOccurred(), guarantee(status == JNI_OK && !env->ExceptionOccurred(),
"register jdk.internal.invoke.NativeEntryPoint natives"); "register jdk.internal.invoke.NativeEntryPoint natives");

View file

@ -59,7 +59,8 @@ static JNINativeMethod PI_methods[] = {
{CC "generateAdapter", CC "(" FOREIGN_ABI "/ABIDescriptor;" FOREIGN_ABI "/BufferLayout;" ")J", FN_PTR(PI_generateAdapter)} {CC "generateAdapter", CC "(" FOREIGN_ABI "/ABIDescriptor;" FOREIGN_ABI "/BufferLayout;" ")J", FN_PTR(PI_generateAdapter)}
}; };
JNI_LEAF(void, JVM_RegisterProgrammableInvokerMethods(JNIEnv *env, jclass PI_class)) JNI_ENTRY(void, JVM_RegisterProgrammableInvokerMethods(JNIEnv *env, jclass PI_class))
ThreadToNativeFromVM ttnfv(thread);
int status = env->RegisterNatives(PI_class, PI_methods, sizeof(PI_methods)/sizeof(JNINativeMethod)); int status = env->RegisterNatives(PI_class, PI_methods, sizeof(PI_methods)/sizeof(JNINativeMethod));
guarantee(status == JNI_OK && !env->ExceptionOccurred(), guarantee(status == JNI_OK && !env->ExceptionOccurred(),
"register jdk.internal.foreign.abi.programmable.ProgrammableInvoker natives"); "register jdk.internal.foreign.abi.programmable.ProgrammableInvoker natives");

View file

@ -22,8 +22,10 @@
*/ */
#include "precompiled.hpp" #include "precompiled.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/symbolTable.hpp" #include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp" #include "classfile/systemDictionary.hpp"
#include "compiler/compilationPolicy.hpp"
#include "memory/resourceArea.hpp" #include "memory/resourceArea.hpp"
#include "prims/universalUpcallHandler.hpp" #include "prims/universalUpcallHandler.hpp"
#include "runtime/interfaceSupport.inline.hpp" #include "runtime/interfaceSupport.inline.hpp"
@ -49,17 +51,29 @@ void ProgrammableUpcallHandler::upcall_helper(JavaThread* thread, jobject rec, a
JavaCalls::call_static(&result, upcall_method.klass, upcall_method.name, upcall_method.sig, &args, CATCH); JavaCalls::call_static(&result, upcall_method.klass, upcall_method.name, upcall_method.sig, &args, CATCH);
} }
void ProgrammableUpcallHandler::attach_thread_and_do_upcall(jobject rec, address buff) { Thread* ProgrammableUpcallHandler::maybe_attach_and_get_thread(bool* should_detach) {
Thread* thread = Thread::current_or_null(); Thread* thread = Thread::current_or_null();
bool should_detach = false;
if (thread == nullptr) { if (thread == nullptr) {
JavaVM_ *vm = (JavaVM *)(&main_vm); JavaVM_ *vm = (JavaVM *)(&main_vm);
JNIEnv* p_env = nullptr; // unused JNIEnv* p_env = nullptr; // unused
jint result = vm->functions->AttachCurrentThread(vm, (void**) &p_env, nullptr); jint result = vm->functions->AttachCurrentThread(vm, (void**) &p_env, nullptr);
guarantee(result == JNI_OK, "Could not attach thread for upcall. JNI error code: %d", result); guarantee(result == JNI_OK, "Could not attach thread for upcall. JNI error code: %d", result);
should_detach = true; *should_detach = true;
thread = Thread::current(); thread = Thread::current();
} else {
*should_detach = false;
} }
return thread;
}
void ProgrammableUpcallHandler::detach_thread(Thread* thread) {
JavaVM_ *vm = (JavaVM *)(&main_vm);
vm->functions->DetachCurrentThread(vm);
}
void ProgrammableUpcallHandler::attach_thread_and_do_upcall(jobject rec, address buff) {
bool should_detach = false;
Thread* thread = maybe_attach_and_get_thread(&should_detach);
{ {
MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXWrite, thread)); MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXWrite, thread));
@ -67,8 +81,7 @@ void ProgrammableUpcallHandler::attach_thread_and_do_upcall(jobject rec, address
} }
if (should_detach) { if (should_detach) {
JavaVM_ *vm = (JavaVM *)(&main_vm); detach_thread(thread);
vm->functions->DetachCurrentThread(vm);
} }
} }
@ -86,30 +99,59 @@ ProgrammableUpcallHandler::ProgrammableUpcallHandler() {
upcall_method.klass = k; upcall_method.klass = k;
upcall_method.name = SymbolTable::new_symbol("invoke"); upcall_method.name = SymbolTable::new_symbol("invoke");
upcall_method.sig = SymbolTable::new_symbol("(L" FOREIGN_ABI "ProgrammableUpcallHandler;J)V"); upcall_method.sig = SymbolTable::new_symbol("(Ljava/lang/invoke/MethodHandle;J)V");
assert(upcall_method.klass->lookup_method(upcall_method.name, upcall_method.sig) != nullptr, assert(upcall_method.klass->lookup_method(upcall_method.name, upcall_method.sig) != nullptr,
"Could not find upcall method: %s.%s%s", upcall_method.klass->external_name(), "Could not find upcall method: %s.%s%s", upcall_method.klass->external_name(),
upcall_method.name->as_C_string(), upcall_method.sig->as_C_string()); upcall_method.name->as_C_string(), upcall_method.sig->as_C_string());
} }
JNI_ENTRY(jlong, PUH_AllocateUpcallStub(JNIEnv *env, jobject rec, jobject abi, jobject buffer_layout)) void ProgrammableUpcallHandler::handle_uncaught_exception(oop exception) {
// Based on CATCH macro
tty->print_cr("Uncaught exception:");
exception->print();
ShouldNotReachHere();
}
JVM_ENTRY(jlong, PUH_AllocateUpcallStub(JNIEnv *env, jclass unused, jobject rec, jobject abi, jobject buffer_layout))
Handle receiver(THREAD, JNIHandles::resolve(rec)); Handle receiver(THREAD, JNIHandles::resolve(rec));
jobject global_rec = JNIHandles::make_global(receiver); jobject global_rec = JNIHandles::make_global(receiver);
return (jlong) ProgrammableUpcallHandler::generate_upcall_stub(global_rec, abi, buffer_layout); return (jlong) ProgrammableUpcallHandler::generate_upcall_stub(global_rec, abi, buffer_layout);
JNI_END JNI_END
JVM_ENTRY(jlong, PUH_AllocateOptimizedUpcallStub(JNIEnv *env, jclass unused, jobject mh, jobject abi, jobject conv))
Handle mh_h(THREAD, JNIHandles::resolve(mh));
jobject mh_j = JNIHandles::make_global(mh_h);
oop lform = java_lang_invoke_MethodHandle::form(mh_h());
oop vmentry = java_lang_invoke_LambdaForm::vmentry(lform);
Method* entry = java_lang_invoke_MemberName::vmtarget(vmentry);
const methodHandle mh_entry(THREAD, entry);
assert(entry->method_holder()->is_initialized(), "no clinit barrier");
CompilationPolicy::compile_if_required(mh_entry, CHECK_0);
return (jlong) ProgrammableUpcallHandler::generate_optimized_upcall_stub(mh_j, entry, abi, conv);
JVM_END
JVM_ENTRY(jboolean, PUH_SupportsOptimizedUpcalls(JNIEnv *env, jclass unused))
return (jboolean) ProgrammableUpcallHandler::supports_optimized_upcalls();
JVM_END
#define CC (char*) /*cast a literal from (const char*)*/ #define CC (char*) /*cast a literal from (const char*)*/
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f) #define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
static JNINativeMethod PUH_methods[] = { static JNINativeMethod PUH_methods[] = {
{CC "allocateUpcallStub", CC "(L" FOREIGN_ABI "ABIDescriptor;L" FOREIGN_ABI "BufferLayout;" ")J", FN_PTR(PUH_AllocateUpcallStub)}, {CC "allocateUpcallStub", CC "(" "Ljava/lang/invoke/MethodHandle;" "L" FOREIGN_ABI "ABIDescriptor;" "L" FOREIGN_ABI "BufferLayout;" ")J", FN_PTR(PUH_AllocateUpcallStub)},
{CC "allocateOptimizedUpcallStub", CC "(" "Ljava/lang/invoke/MethodHandle;" "L" FOREIGN_ABI "ABIDescriptor;" "L" FOREIGN_ABI "ProgrammableUpcallHandler$CallRegs;" ")J", FN_PTR(PUH_AllocateOptimizedUpcallStub)},
{CC "supportsOptimizedUpcalls", CC "()Z", FN_PTR(PUH_SupportsOptimizedUpcalls)},
}; };
/** /**
* This one function is exported, used by NativeLookup. * This one function is exported, used by NativeLookup.
*/ */
JNI_LEAF(void, JVM_RegisterProgrammableUpcallHandlerMethods(JNIEnv *env, jclass PUH_class)) JNI_ENTRY(void, JVM_RegisterProgrammableUpcallHandlerMethods(JNIEnv *env, jclass PUH_class))
ThreadToNativeFromVM ttnfv(thread);
int status = env->RegisterNatives(PUH_class, PUH_methods, sizeof(PUH_methods)/sizeof(JNINativeMethod)); int status = env->RegisterNatives(PUH_class, PUH_methods, sizeof(PUH_methods)/sizeof(JNINativeMethod));
guarantee(status == JNI_OK && !env->ExceptionOccurred(), guarantee(status == JNI_OK && !env->ExceptionOccurred(),
"register jdk.internal.foreign.abi.ProgrammableUpcallHandler natives"); "register jdk.internal.foreign.abi.ProgrammableUpcallHandler natives");

View file

@ -45,8 +45,14 @@ private:
static void upcall_helper(JavaThread* thread, jobject rec, address buff); static void upcall_helper(JavaThread* thread, jobject rec, address buff);
static void attach_thread_and_do_upcall(jobject rec, address buff); static void attach_thread_and_do_upcall(jobject rec, address buff);
static void handle_uncaught_exception(oop exception);
static Thread* maybe_attach_and_get_thread(bool* should_detach);
static void detach_thread(Thread* thread);
public: public:
static address generate_optimized_upcall_stub(jobject mh, Method* entry, jobject jabi, jobject jconv);
static address generate_upcall_stub(jobject rec, jobject abi, jobject buffer_layout); static address generate_upcall_stub(jobject rec, jobject abi, jobject buffer_layout);
static bool supports_optimized_upcalls();
}; };
#endif // SHARE_VM_PRIMS_UNIVERSALUPCALLHANDLER_HPP #endif // SHARE_VM_PRIMS_UNIVERSALUPCALLHANDLER_HPP

View file

@ -36,8 +36,14 @@ JVM_ENTRY(static jboolean, UH_FreeUpcallStub0(JNIEnv *env, jobject _unused, jlon
return false; return false;
} }
//free global JNI handle //free global JNI handle
jobject* rec_ptr = (jobject*)(void*)cb -> content_begin(); jobject handle = NULL;
JNIHandles::destroy_global(*rec_ptr); if (cb->is_optimized_entry_blob()) {
handle = ((OptimizedEntryBlob*)cb)->receiver();
} else {
jobject* handle_ptr = (jobject*)(void*)cb->content_begin();
handle = *handle_ptr;
}
JNIHandles::destroy_global(handle);
//free code blob //free code blob
CodeCache::free(cb); CodeCache::free(cb);
return true; return true;

View file

@ -2300,13 +2300,13 @@ WB_ENTRY(void, WB_CheckThreadObjOfTerminatingThread(JNIEnv* env, jobject wb, job
} }
WB_END WB_END
WB_ENTRY(void, WB_VerifyFrames(JNIEnv* env, jobject wb, jboolean log)) WB_ENTRY(void, WB_VerifyFrames(JNIEnv* env, jobject wb, jboolean log, jboolean update_map))
intx tty_token = -1; intx tty_token = -1;
if (log) { if (log) {
tty_token = ttyLocker::hold_tty(); tty_token = ttyLocker::hold_tty();
tty->print_cr("[WhiteBox::VerifyFrames] Walking Frames"); tty->print_cr("[WhiteBox::VerifyFrames] Walking Frames");
} }
for (StackFrameStream fst(JavaThread::current(), true, true); !fst.is_done(); fst.next()) { for (StackFrameStream fst(JavaThread::current(), update_map, true); !fst.is_done(); fst.next()) {
frame* current_frame = fst.current(); frame* current_frame = fst.current();
if (log) { if (log) {
current_frame->print_value(); current_frame->print_value();
@ -2566,7 +2566,7 @@ static JNINativeMethod methods[] = {
{CC"handshakeWalkStack", CC"(Ljava/lang/Thread;Z)I", (void*)&WB_HandshakeWalkStack }, {CC"handshakeWalkStack", CC"(Ljava/lang/Thread;Z)I", (void*)&WB_HandshakeWalkStack },
{CC"asyncHandshakeWalkStack", CC"(Ljava/lang/Thread;)V", (void*)&WB_AsyncHandshakeWalkStack }, {CC"asyncHandshakeWalkStack", CC"(Ljava/lang/Thread;)V", (void*)&WB_AsyncHandshakeWalkStack },
{CC"checkThreadObjOfTerminatingThread", CC"(Ljava/lang/Thread;)V", (void*)&WB_CheckThreadObjOfTerminatingThread }, {CC"checkThreadObjOfTerminatingThread", CC"(Ljava/lang/Thread;)V", (void*)&WB_CheckThreadObjOfTerminatingThread },
{CC"verifyFrames", CC"(Z)V", (void*)&WB_VerifyFrames }, {CC"verifyFrames", CC"(ZZ)V", (void*)&WB_VerifyFrames },
{CC"addCompilerDirective", CC"(Ljava/lang/String;)I", {CC"addCompilerDirective", CC"(Ljava/lang/String;)I",
(void*)&WB_AddCompilerDirective }, (void*)&WB_AddCompilerDirective },
{CC"removeCompilerDirective", CC"(I)V", (void*)&WB_RemoveCompilerDirective }, {CC"removeCompilerDirective", CC"(I)V", (void*)&WB_RemoveCompilerDirective },

View file

@ -309,6 +309,8 @@ bool needs_module_property_warning = false;
#define PATH_LEN 4 #define PATH_LEN 4
#define UPGRADE_PATH "upgrade.path" #define UPGRADE_PATH "upgrade.path"
#define UPGRADE_PATH_LEN 12 #define UPGRADE_PATH_LEN 12
#define ENABLE_NATIVE_ACCESS "enable.native.access"
#define ENABLE_NATIVE_ACCESS_LEN 20
void Arguments::add_init_library(const char* name, char* options) { void Arguments::add_init_library(const char* name, char* options) {
_libraryList.add(new AgentLibrary(name, options, false, NULL)); _libraryList.add(new AgentLibrary(name, options, false, NULL));
@ -347,7 +349,8 @@ bool Arguments::is_internal_module_property(const char* property) {
matches_property_suffix(property_suffix, ADDMODS, ADDMODS_LEN) || matches_property_suffix(property_suffix, ADDMODS, ADDMODS_LEN) ||
matches_property_suffix(property_suffix, LIMITMODS, LIMITMODS_LEN) || matches_property_suffix(property_suffix, LIMITMODS, LIMITMODS_LEN) ||
matches_property_suffix(property_suffix, PATH, PATH_LEN) || matches_property_suffix(property_suffix, PATH, PATH_LEN) ||
matches_property_suffix(property_suffix, UPGRADE_PATH, UPGRADE_PATH_LEN)) { matches_property_suffix(property_suffix, UPGRADE_PATH, UPGRADE_PATH_LEN) ||
matches_property_suffix(property_suffix, ENABLE_NATIVE_ACCESS, ENABLE_NATIVE_ACCESS_LEN)) {
return true; return true;
} }
} }
@ -1964,6 +1967,7 @@ unsigned int addexports_count = 0;
unsigned int addopens_count = 0; unsigned int addopens_count = 0;
unsigned int addmods_count = 0; unsigned int addmods_count = 0;
unsigned int patch_mod_count = 0; unsigned int patch_mod_count = 0;
unsigned int enable_native_access_count = 0;
// Check the consistency of vm_init_args // Check the consistency of vm_init_args
bool Arguments::check_vm_args_consistency() { bool Arguments::check_vm_args_consistency() {
@ -2406,6 +2410,10 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m
if (!create_numbered_module_property("jdk.module.addmods", tail, addmods_count++)) { if (!create_numbered_module_property("jdk.module.addmods", tail, addmods_count++)) {
return JNI_ENOMEM; return JNI_ENOMEM;
} }
} else if (match_option(option, "--enable-native-access=", &tail)) {
if (!create_numbered_module_property("jdk.module.enable.native.access", tail, enable_native_access_count++)) {
return JNI_ENOMEM;
}
} else if (match_option(option, "--limit-modules=", &tail)) { } else if (match_option(option, "--limit-modules=", &tail)) {
if (!create_module_property("jdk.module.limitmods", tail, InternalProperty)) { if (!create_module_property("jdk.module.limitmods", tail, InternalProperty)) {
return JNI_ENOMEM; return JNI_ENOMEM;

View file

@ -158,7 +158,7 @@ void frame::set_pc(address newpc ) {
} }
#endif // ASSERT #endif // ASSERT
// Unsafe to use the is_deoptimzed tester after changing pc // Unsafe to use the is_deoptimized tester after changing pc
_deopt_state = unknown; _deopt_state = unknown;
_pc = newpc; _pc = newpc;
_cb = CodeCache::find_blob_unsafe(_pc); _cb = CodeCache::find_blob_unsafe(_pc);
@ -1067,6 +1067,10 @@ void frame::oops_do_internal(OopClosure* f, CodeBlobClosure* cf, const RegisterM
oops_interpreted_do(f, map, use_interpreter_oop_map_cache); oops_interpreted_do(f, map, use_interpreter_oop_map_cache);
} else if (is_entry_frame()) { } else if (is_entry_frame()) {
oops_entry_do(f, map); oops_entry_do(f, map);
} else if (is_optimized_entry_frame()) {
// Nothing to do
// receiver is a global ref
// handle block is for JNI
} else if (CodeCache::contains(pc())) { } else if (CodeCache::contains(pc())) {
oops_code_blob_do(f, cf, map, derived_mode); oops_code_blob_do(f, cf, map, derived_mode);
} else { } else {
@ -1105,8 +1109,10 @@ void frame::verify(const RegisterMap* map) const {
#if COMPILER2_OR_JVMCI #if COMPILER2_OR_JVMCI
assert(DerivedPointerTable::is_empty(), "must be empty before verify"); assert(DerivedPointerTable::is_empty(), "must be empty before verify");
#endif #endif
if (map->update_map()) { // The map has to be up-to-date for the current frame
oops_do_internal(&VerifyOopClosure::verify_oop, NULL, map, false, DerivedPointerIterationMode::_ignore); oops_do_internal(&VerifyOopClosure::verify_oop, NULL, map, false, DerivedPointerIterationMode::_ignore);
} }
}
#ifdef ASSERT #ifdef ASSERT

View file

@ -138,6 +138,7 @@ class frame {
bool is_compiled_frame() const; bool is_compiled_frame() const;
bool is_safepoint_blob_frame() const; bool is_safepoint_blob_frame() const;
bool is_deoptimized_frame() const; bool is_deoptimized_frame() const;
bool is_optimized_entry_frame() const;
// testers // testers
bool is_first_frame() const; // oldest frame? (has no sender) bool is_first_frame() const; // oldest frame? (has no sender)
@ -172,6 +173,7 @@ class frame {
frame sender_for_entry_frame(RegisterMap* map) const; frame sender_for_entry_frame(RegisterMap* map) const;
frame sender_for_interpreter_frame(RegisterMap* map) const; frame sender_for_interpreter_frame(RegisterMap* map) const;
frame sender_for_native_frame(RegisterMap* map) const; frame sender_for_native_frame(RegisterMap* map) const;
frame sender_for_optimized_entry_frame(RegisterMap* map) const;
bool is_entry_frame_valid(JavaThread* thread) const; bool is_entry_frame_valid(JavaThread* thread) const;

View file

@ -53,6 +53,10 @@ inline bool frame::is_first_frame() const {
return is_entry_frame() && entry_frame_is_first(); return is_entry_frame() && entry_frame_is_first();
} }
inline bool frame::is_optimized_entry_frame() const {
return _cb != NULL && _cb->is_optimized_entry_blob();
}
inline address frame::oopmapreg_to_location(VMReg reg, const RegisterMap* reg_map) const { inline address frame::oopmapreg_to_location(VMReg reg, const RegisterMap* reg_map) const {
if(reg->is_reg()) { if(reg->is_reg()) {
// If it is passed in a register, it got spilled in the stub frame. // If it is passed in a register, it got spilled in the stub frame.

View file

@ -2080,6 +2080,9 @@ const intx ObjectAlignmentInBytes = 8;
false AARCH64_ONLY(DEBUG_ONLY(||true)), \ false AARCH64_ONLY(DEBUG_ONLY(||true)), \
"Mark all threads after a safepoint, and clear on a modify " \ "Mark all threads after a safepoint, and clear on a modify " \
"fence. Add cleanliness checks.") \ "fence. Add cleanliness checks.") \
\
develop(bool, TraceOptimizedUpcallStubs, false, \
"Trace optimized upcall stub generation") \
// end of RUNTIME_FLAGS // end of RUNTIME_FLAGS

View file

@ -764,7 +764,6 @@ static Handle new_type(Symbol* signature, Klass* k, TRAPS) {
return Handle(THREAD, nt); return Handle(THREAD, nt);
} }
oop Reflection::new_method(const methodHandle& method, bool for_constant_pool_access, TRAPS) { oop Reflection::new_method(const methodHandle& method, bool for_constant_pool_access, TRAPS) {
// Allow sun.reflect.ConstantPool to refer to <clinit> methods as java.lang.reflect.Methods. // Allow sun.reflect.ConstantPool to refer to <clinit> methods as java.lang.reflect.Methods.
assert(!method()->is_initializer() || assert(!method()->is_initializer() ||

View file

@ -512,6 +512,9 @@ address SharedRuntime::raw_exception_handler_for_return_address(JavaThread* curr
// JavaCallWrapper::~JavaCallWrapper // JavaCallWrapper::~JavaCallWrapper
return StubRoutines::catch_exception_entry(); return StubRoutines::catch_exception_entry();
} }
if (blob != NULL && blob->is_optimized_entry_blob()) {
return ((OptimizedEntryBlob*)blob)->exception_handler();
}
// Interpreted code // Interpreted code
if (Interpreter::contains(return_address)) { if (Interpreter::contains(return_address)) {
// The deferred StackWatermarkSet::after_unwind check will be performed in // The deferred StackWatermarkSet::after_unwind check will be performed in
@ -1439,7 +1442,7 @@ JRT_BLOCK_ENTRY(address, SharedRuntime::handle_wrong_method_ic_miss(JavaThread*
frame stub_frame = current->last_frame(); frame stub_frame = current->last_frame();
assert(stub_frame.is_runtime_frame(), "sanity check"); assert(stub_frame.is_runtime_frame(), "sanity check");
frame caller_frame = stub_frame.sender(&reg_map); frame caller_frame = stub_frame.sender(&reg_map);
assert(!caller_frame.is_interpreted_frame() && !caller_frame.is_entry_frame(), "unexpected frame"); assert(!caller_frame.is_interpreted_frame() && !caller_frame.is_entry_frame() && !caller_frame.is_optimized_entry_frame(), "unexpected frame");
#endif /* ASSERT */ #endif /* ASSERT */
methodHandle callee_method; methodHandle callee_method;
@ -1471,7 +1474,8 @@ JRT_BLOCK_ENTRY(address, SharedRuntime::handle_wrong_method(JavaThread* current)
frame caller_frame = stub_frame.sender(&reg_map); frame caller_frame = stub_frame.sender(&reg_map);
if (caller_frame.is_interpreted_frame() || if (caller_frame.is_interpreted_frame() ||
caller_frame.is_entry_frame()) { caller_frame.is_entry_frame() ||
caller_frame.is_optimized_entry_frame()) {
Method* callee = current->callee_target(); Method* callee = current->callee_target();
guarantee(callee != NULL && callee->is_method(), "bad handshake"); guarantee(callee != NULL && callee->is_method(), "bad handshake");
current->set_vm_result_2(callee); current->set_vm_result_2(callee);

View file

@ -462,6 +462,11 @@ class SharedRuntime: AllStatic {
static void save_native_result(MacroAssembler *_masm, BasicType ret_type, int frame_slots); static void save_native_result(MacroAssembler *_masm, BasicType ret_type, int frame_slots);
static void restore_native_result(MacroAssembler *_masm, BasicType ret_type, int frame_slots); static void restore_native_result(MacroAssembler *_masm, BasicType ret_type, int frame_slots);
static void move32_64(MacroAssembler* masm, VMRegPair src, VMRegPair dst);
static void long_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst);
static void float_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst);
static void double_move(MacroAssembler* masm, VMRegPair src, VMRegPair dst);
// Generate a native wrapper for a given method. The method takes arguments // Generate a native wrapper for a given method. The method takes arguments
// in the Java compiled code convention, marshals them to the native // in the Java compiled code convention, marshals them to the native
// convention (handlizes oops, etc), transitions to native, makes the call, // convention (handlizes oops, etc), transitions to native, makes the call,
@ -513,6 +518,12 @@ class SharedRuntime: AllStatic {
const GrowableArray<VMReg>& output_registers); const GrowableArray<VMReg>& output_registers);
#endif #endif
static void compute_move_order(const BasicType* in_sig_bt,
int total_in_args, const VMRegPair* in_regs,
int total_out_args, VMRegPair* out_regs,
GrowableArray<int>& arg_order,
VMRegPair tmp_vmreg);
#ifndef PRODUCT #ifndef PRODUCT
// Collect and print inline cache miss statistics // Collect and print inline cache miss statistics

View file

@ -109,6 +109,8 @@ public final class Module implements AnnotatedElement {
// the module descriptor // the module descriptor
private final ModuleDescriptor descriptor; private final ModuleDescriptor descriptor;
// true, if this module allows restricted native access
private volatile boolean enableNativeAccess;
/** /**
* Creates a new named Module. The resulting Module will be defined to the * Creates a new named Module. The resulting Module will be defined to the
@ -133,6 +135,10 @@ public final class Module implements AnnotatedElement {
String loc = Objects.toString(uri, null); String loc = Objects.toString(uri, null);
Object[] packages = descriptor.packages().toArray(); Object[] packages = descriptor.packages().toArray();
defineModule0(this, isOpen, vs, loc, packages); defineModule0(this, isOpen, vs, loc, packages);
if (loader == null || loader == ClassLoaders.platformClassLoader()) {
// boot/builtin modules are always native
implAddEnableNativeAccess();
}
} }
@ -244,6 +250,30 @@ public final class Module implements AnnotatedElement {
return null; return null;
} }
/**
* Update this module to allow access to restricted methods.
*/
Module implAddEnableNativeAccess() {
enableNativeAccess = true;
return this;
}
/**
* Update all unnamed modules to allow access to restricted methods.
*/
static void implAddEnableNativeAccessAllUnnamed() {
ALL_UNNAMED_MODULE.enableNativeAccess = true;
}
/**
* Returns true if module m can access restricted methods.
*/
boolean implIsEnableNativeAccess() {
return isNamed() ?
enableNativeAccess :
ALL_UNNAMED_MODULE.enableNativeAccess;
}
// -- // --
// special Module to mean "all unnamed modules" // special Module to mean "all unnamed modules"

View file

@ -62,7 +62,6 @@ import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.misc.Unsafe; import jdk.internal.misc.Unsafe;
import jdk.internal.util.StaticProperty; import jdk.internal.util.StaticProperty;
import jdk.internal.module.ModuleBootstrap; import jdk.internal.module.ModuleBootstrap;
@ -2274,6 +2273,15 @@ public final class System {
public boolean isReflectivelyOpened(Module m, String pn, Module other) { public boolean isReflectivelyOpened(Module m, String pn, Module other) {
return m.isReflectivelyOpened(pn, other); return m.isReflectivelyOpened(pn, other);
} }
public Module addEnableNativeAccess(Module m) {
return m.implAddEnableNativeAccess();
}
public void addEnableNativeAccessAllUnnamed() {
Module.implAddEnableNativeAccessAllUnnamed();
}
public boolean isEnableNativeAccess(Module m) {
return m.implIsEnableNativeAccess();
}
public ServicesCatalog getServicesCatalog(ModuleLayer layer) { public ServicesCatalog getServicesCatalog(ModuleLayer layer) {
return layer.getServicesCatalog(); return layer.getServicesCatalog();
} }

View file

@ -1467,6 +1467,11 @@ abstract class MethodHandleImpl {
return GenerateJLIClassesHelper.generateHolderClasses(traces); return GenerateJLIClassesHelper.generateHolderClasses(traces);
} }
@Override
public void ensureCustomized(MethodHandle mh) {
mh.customize();
}
@Override @Override
public VarHandle memoryAccessVarHandle(Class<?> carrier, boolean skipAlignmentMaskCheck, long alignmentMask, public VarHandle memoryAccessVarHandle(Class<?> carrier, boolean skipAlignmentMaskCheck, long alignmentMask,
ByteOrder order) { ByteOrder order) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -58,7 +58,7 @@ import static java.lang.invoke.MethodHandleStatics.newInternalError;
throw new IllegalArgumentException("Type must only contain primitives: " + type); throw new IllegalArgumentException("Type must only contain primitives: " + type);
if (type != fallback.type()) if (type != fallback.type())
throw new IllegalArgumentException("Type of fallback must match"); throw new IllegalArgumentException("Type of fallback must match: " + type + " != " + fallback.type());
LambdaForm lform = preparedLambdaForm(type); LambdaForm lform = preparedLambdaForm(type);
return new NativeMethodHandle(type, lform, fallback, nep); return new NativeMethodHandle(type, lform, fallback, nep);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -30,6 +30,7 @@ import jdk.internal.access.SharedSecrets;
import jdk.internal.access.foreign.MemorySegmentProxy; import jdk.internal.access.foreign.MemorySegmentProxy;
import jdk.internal.access.foreign.UnmapperProxy; import jdk.internal.access.foreign.UnmapperProxy;
import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.misc.ScopedMemoryAccess;
import jdk.internal.misc.ScopedMemoryAccess.Scope;
import jdk.internal.misc.Unsafe; import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM.BufferPool; import jdk.internal.misc.VM.BufferPool;
import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.ForceInline;
@ -821,6 +822,18 @@ public abstract class Buffer {
return buffer.segment; return buffer.segment;
} }
@Override
public Scope.Handle acquireScope(Buffer buffer, boolean async) {
var scope = buffer.scope();
if (scope == null) {
return null;
}
if (async && scope.ownerThread() != null) {
throw new IllegalStateException("Confined scope not supported");
}
return scope.acquire();
}
@Override @Override
public void force(FileDescriptor fd, long address, boolean isSync, long offset, long size) { public void force(FileDescriptor fd, long address, boolean isSync, long offset, long size) {
MappedMemoryUtils.force(fd, address, isSync, offset, size); MappedMemoryUtils.force(fd, address, isSync, offset, size);

View file

@ -256,6 +256,21 @@ public interface JavaLangAccess {
*/ */
boolean isReflectivelyOpened(Module module, String pn, Module other); boolean isReflectivelyOpened(Module module, String pn, Module other);
/**
* Updates module m to allow access to restricted methods.
*/
Module addEnableNativeAccess(Module m);
/**
* Updates all unnamed modules to allow access to restricted methods.
*/
void addEnableNativeAccessAllUnnamed();
/**
* Returns true if module m can access restricted methods.
*/
boolean isEnableNativeAccess(Module m);
/** /**
* Returns the ServicesCatalog for the given Layer. * Returns the ServicesCatalog for the given Layer.
*/ */

View file

@ -132,4 +132,11 @@ public interface JavaLangInvokeAccess {
* @return the native method handle * @return the native method handle
*/ */
MethodHandle nativeMethodHandle(NativeEntryPoint nep, MethodHandle fallback); MethodHandle nativeMethodHandle(NativeEntryPoint nep, MethodHandle fallback);
/**
* Ensure given method handle is customized
*
* @param mh the method handle
*/
void ensureCustomized(MethodHandle mh);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,7 @@ package jdk.internal.access;
import jdk.internal.access.foreign.MemorySegmentProxy; import jdk.internal.access.foreign.MemorySegmentProxy;
import jdk.internal.access.foreign.UnmapperProxy; import jdk.internal.access.foreign.UnmapperProxy;
import jdk.internal.misc.ScopedMemoryAccess.Scope;
import jdk.internal.misc.VM.BufferPool; import jdk.internal.misc.VM.BufferPool;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@ -85,6 +86,14 @@ public interface JavaNioAccess {
*/ */
MemorySegmentProxy bufferSegment(Buffer buffer); MemorySegmentProxy bufferSegment(Buffer buffer);
/**
* Used by I/O operations to make a buffer's resource scope non-closeable
* (for the duration of the I/O operation) by acquiring a new resource
* scope handle. Null is returned if the buffer has no scope, or
* acquiring is not required to guarantee safety.
*/
Scope.Handle acquireScope(Buffer buffer, boolean async);
/** /**
* Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl} and byte buffer var handle views. * Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl} and byte buffer var handle views.
*/ */

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -37,8 +37,6 @@ public class NativeEntryPoint {
registerNatives(); registerNatives();
} }
private final long addr;
private final int shadowSpace; private final int shadowSpace;
// encoded as VMRegImpl* // encoded as VMRegImpl*
@ -49,9 +47,8 @@ public class NativeEntryPoint {
private final MethodType methodType; // C2 sees erased version (byte -> int), so need this explicitly private final MethodType methodType; // C2 sees erased version (byte -> int), so need this explicitly
private final String name; private final String name;
private NativeEntryPoint(long addr, int shadowSpace, long[] argMoves, long[] returnMoves, private NativeEntryPoint(int shadowSpace, long[] argMoves, long[] returnMoves,
boolean needTransition, MethodType methodType, String name) { boolean needTransition, MethodType methodType, String name) {
this.addr = addr;
this.shadowSpace = shadowSpace; this.shadowSpace = shadowSpace;
this.argMoves = Objects.requireNonNull(argMoves); this.argMoves = Objects.requireNonNull(argMoves);
this.returnMoves = Objects.requireNonNull(returnMoves); this.returnMoves = Objects.requireNonNull(returnMoves);
@ -60,14 +57,15 @@ public class NativeEntryPoint {
this.name = name; this.name = name;
} }
public static NativeEntryPoint make(long addr, String name, ABIDescriptorProxy abi, VMStorageProxy[] argMoves, VMStorageProxy[] returnMoves, public static NativeEntryPoint make(String name, ABIDescriptorProxy abi,
VMStorageProxy[] argMoves, VMStorageProxy[] returnMoves,
boolean needTransition, MethodType methodType) { boolean needTransition, MethodType methodType) {
if (returnMoves.length > 1) { if (returnMoves.length > 1) {
throw new IllegalArgumentException("Multiple register return not supported"); throw new IllegalArgumentException("Multiple register return not supported");
} }
return new NativeEntryPoint( return new NativeEntryPoint(abi.shadowSpaceBytes(), encodeVMStorages(argMoves), encodeVMStorages(returnMoves),
addr, abi.shadowSpaceBytes(), encodeVMStorages(argMoves), encodeVMStorages(returnMoves), needTransition, methodType, name); needTransition, methodType, name);
} }
private static long[] encodeVMStorages(VMStorageProxy[] moves) { private static long[] encodeVMStorages(VMStorageProxy[] moves) {

View file

@ -62,7 +62,9 @@ public class BootLoader {
private static final String JAVA_HOME = StaticProperty.javaHome(); private static final String JAVA_HOME = StaticProperty.javaHome();
static { static {
UNNAMED_MODULE = SharedSecrets.getJavaLangAccess().defineUnnamedModule(null); JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
UNNAMED_MODULE = jla.defineUnnamedModule(null);
jla.addEnableNativeAccess(UNNAMED_MODULE);
setBootLoaderUnnamedModule0(UNNAMED_MODULE); setBootLoaderUnnamedModule0(UNNAMED_MODULE);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -71,7 +71,7 @@ import jdk.internal.vm.annotation.ForceInline;
*/ */
public class ScopedMemoryAccess { public class ScopedMemoryAccess {
private static Unsafe UNSAFE = Unsafe.getUnsafe(); private static final Unsafe UNSAFE = Unsafe.getUnsafe();
private static native void registerNatives(); private static native void registerNatives();
static { static {
@ -97,10 +97,21 @@ public class ScopedMemoryAccess {
* which embodies the temporal checks associated with a given memory region. * which embodies the temporal checks associated with a given memory region.
*/ */
public interface Scope { public interface Scope {
interface Handle {
Scope scope();
}
void checkValidState(); void checkValidState();
Thread ownerThread(); Thread ownerThread();
boolean isImplicit();
Handle acquire();
void release(Handle handle);
/** /**
* Error thrown when memory access fails because the memory has already been released. * Error thrown when memory access fails because the memory has already been released.
* Note: for performance reasons, this exception is never created by client; instead a shared instance * Note: for performance reasons, this exception is never created by client; instead a shared instance

View file

@ -38,6 +38,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -264,7 +265,9 @@ public final class ModuleBootstrap {
if (baseUri == null) if (baseUri == null)
throw new InternalError(JAVA_BASE + " does not have a location"); throw new InternalError(JAVA_BASE + " does not have a location");
BootLoader.loadModule(base); BootLoader.loadModule(base);
Modules.defineModule(null, base.descriptor(), baseUri);
Module baseModule = Modules.defineModule(null, base.descriptor(), baseUri);
JLA.addEnableNativeAccess(baseModule);
// Step 2a: Scan all modules when --validate-modules specified // Step 2a: Scan all modules when --validate-modules specified
@ -455,6 +458,9 @@ public final class ModuleBootstrap {
addExtraReads(bootLayer); addExtraReads(bootLayer);
boolean extraExportsOrOpens = addExtraExportsAndOpens(bootLayer); boolean extraExportsOrOpens = addExtraExportsAndOpens(bootLayer);
// add enable native access
addEnableNativeAccess(bootLayer);
Counters.add("jdk.module.boot.7.adjustModulesTime"); Counters.add("jdk.module.boot.7.adjustModulesTime");
// save module finders for later use // save module finders for later use
@ -766,6 +772,47 @@ public final class ModuleBootstrap {
} }
} }
/**
* Process the --enable-native-access option to grant access to restricted methods to selected modules.
*/
private static void addEnableNativeAccess(ModuleLayer layer) {
for (String name : decodeEnableNativeAccess()) {
if (name.equals("ALL-UNNAMED")) {
JLA.addEnableNativeAccessAllUnnamed();
} else {
Optional<Module> module = layer.findModule(name);
if (module.isPresent()) {
JLA.addEnableNativeAccess(module.get());
} else {
warnUnknownModule(ENABLE_NATIVE_ACCESS, name);
}
}
}
}
/**
* Returns the set of module names specified by --enable-native-access options.
*/
private static Set<String> decodeEnableNativeAccess() {
String prefix = "jdk.module.enable.native.access.";
int index = 0;
// the system property is removed after decoding
String value = getAndRemoveProperty(prefix + index);
Set<String> modules = new HashSet<>();
if (value == null) {
return modules;
}
while (value != null) {
for (String s : value.split(",")) {
if (!s.isEmpty())
modules.add(s);
}
index++;
value = getAndRemoveProperty(prefix + index);
}
return modules;
}
/** /**
* Decodes the values of --add-reads, -add-exports, --add-opens or * Decodes the values of --add-reads, -add-exports, --add-opens or
* --patch-modules options that are encoded in system properties. * --patch-modules options that are encoded in system properties.
@ -889,7 +936,7 @@ public final class ModuleBootstrap {
private static final String ADD_OPENS = "--add-opens"; private static final String ADD_OPENS = "--add-opens";
private static final String ADD_READS = "--add-reads"; private static final String ADD_READS = "--add-reads";
private static final String PATCH_MODULE = "--patch-module"; private static final String PATCH_MODULE = "--patch-module";
private static final String ENABLE_NATIVE_ACCESS = "--enable-native-access";
/* /*
* Returns the command-line option name corresponds to the specified * Returns the command-line option name corresponds to the specified

View file

@ -106,6 +106,13 @@ public class Reflection {
} }
} }
public static void ensureNativeAccess(Class<?> currentClass) {
Module module = currentClass.getModule();
if (!SharedSecrets.getJavaLangAccess().isEnableNativeAccess(module)) {
throw new IllegalCallerException("Illegal native access from: " + module);
}
}
/** /**
* Verify access to a member and return {@code true} if it is granted. * Verify access to a member and return {@code true} if it is granted.
* *

View file

@ -212,7 +212,8 @@ module java.base {
jdk.jartool, jdk.jartool,
jdk.jfr, jdk.jfr,
jdk.jlink, jdk.jlink,
jdk.jpackage; jdk.jpackage,
jdk.incubator.foreign;
exports jdk.internal.perf to exports jdk.internal.perf to
java.management, java.management,
jdk.management.agent, jdk.management.agent,
@ -229,7 +230,8 @@ module java.base {
java.sql.rowset, java.sql.rowset,
jdk.dynalink, jdk.dynalink,
jdk.internal.vm.ci, jdk.internal.vm.ci,
jdk.unsupported; jdk.unsupported,
jdk.incubator.foreign;
exports jdk.internal.vm to exports jdk.internal.vm to
jdk.internal.jvmstat, jdk.internal.jvmstat,
jdk.management.agent; jdk.management.agent;

View file

@ -60,6 +60,9 @@ java.launcher.opt.footer = \
\ root modules to resolve in addition to the initial module.\n\ \ root modules to resolve in addition to the initial module.\n\
\ <module name> can also be ALL-DEFAULT, ALL-SYSTEM,\n\ \ <module name> can also be ALL-DEFAULT, ALL-SYSTEM,\n\
\ ALL-MODULE-PATH.\n\ \ ALL-MODULE-PATH.\n\
\ --enable-native-access <module name>[,<module name>...]\n\
\ modules that are permitted to perform restricted native operations.\n\
\ <module name> can also be ALL-UNNAMED.\n\
\ --list-modules\n\ \ --list-modules\n\
\ list observable modules and exit\n\ \ list observable modules and exit\n\
\ -d <module name>\n\ \ -d <module name>\n\

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -28,7 +28,10 @@ package sun.nio.ch;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Objects;
import jdk.internal.access.JavaNioAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.ScopedMemoryAccess.Scope;
/** /**
* File-descriptor based I/O utilities that are shared by NIO classes. * File-descriptor based I/O utilities that are shared by NIO classes.
@ -47,15 +50,30 @@ public class IOUtil {
NativeDispatcher nd) NativeDispatcher nd)
throws IOException throws IOException
{ {
return write(fd, src, position, false, -1, nd); return write(fd, src, position, false, false, -1, nd);
}
static int write(FileDescriptor fd, ByteBuffer src, long position,
boolean async, NativeDispatcher nd)
throws IOException
{
return write(fd, src, position, false, async, -1, nd);
} }
static int write(FileDescriptor fd, ByteBuffer src, long position, static int write(FileDescriptor fd, ByteBuffer src, long position,
boolean directIO, int alignment, NativeDispatcher nd) boolean directIO, int alignment, NativeDispatcher nd)
throws IOException throws IOException
{
return write(fd, src, position, directIO, false, alignment, nd);
}
static int write(FileDescriptor fd, ByteBuffer src, long position,
boolean directIO, boolean async, int alignment,
NativeDispatcher nd)
throws IOException
{ {
if (src instanceof DirectBuffer) { if (src instanceof DirectBuffer) {
return writeFromNativeBuffer(fd, src, position, directIO, alignment, nd); return writeFromNativeBuffer(fd, src, position, directIO, async, alignment, nd);
} }
// Substitute a native buffer // Substitute a native buffer
@ -76,7 +94,7 @@ public class IOUtil {
// Do not update src until we see how many bytes were written // Do not update src until we see how many bytes were written
src.position(pos); src.position(pos);
int n = writeFromNativeBuffer(fd, bb, position, directIO, alignment, nd); int n = writeFromNativeBuffer(fd, bb, position, directIO, async, alignment, nd);
if (n > 0) { if (n > 0) {
// now update src // now update src
src.position(pos + n); src.position(pos + n);
@ -89,7 +107,8 @@ public class IOUtil {
private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb, private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb,
long position, boolean directIO, long position, boolean directIO,
int alignment, NativeDispatcher nd) boolean async, int alignment,
NativeDispatcher nd)
throws IOException throws IOException
{ {
int pos = bb.position(); int pos = bb.position();
@ -105,46 +124,62 @@ public class IOUtil {
int written = 0; int written = 0;
if (rem == 0) if (rem == 0)
return 0; return 0;
var handle = acquireScope(bb, async);
try {
if (position != -1) { if (position != -1) {
written = nd.pwrite(fd, written = nd.pwrite(fd, bufferAddress(bb) + pos, rem, position);
((DirectBuffer)bb).address() + pos,
rem, position);
} else { } else {
written = nd.write(fd, ((DirectBuffer)bb).address() + pos, rem); written = nd.write(fd, bufferAddress(bb) + pos, rem);
}
} finally {
releaseScope(handle);
} }
if (written > 0) if (written > 0)
bb.position(pos + written); bb.position(pos + written);
return written; return written;
} }
static long write(FileDescriptor fd, ByteBuffer[] bufs, NativeDispatcher nd) static long write(FileDescriptor fd, ByteBuffer[] bufs, boolean async,
NativeDispatcher nd)
throws IOException throws IOException
{ {
return write(fd, bufs, 0, bufs.length, false, -1, nd); return write(fd, bufs, 0, bufs.length, false, async, -1, nd);
} }
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
NativeDispatcher nd) NativeDispatcher nd)
throws IOException throws IOException
{ {
return write(fd, bufs, offset, length, false, -1, nd); return write(fd, bufs, offset, length, false, false, -1, nd);
} }
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, int alignment, NativeDispatcher nd) boolean direct, int alignment, NativeDispatcher nd)
throws IOException
{
return write(fd, bufs, offset, length, direct, false, alignment, nd);
}
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, boolean async,
int alignment, NativeDispatcher nd)
throws IOException throws IOException
{ {
IOVecWrapper vec = IOVecWrapper.get(length); IOVecWrapper vec = IOVecWrapper.get(length);
boolean completed = false; boolean completed = false;
int iov_len = 0; int iov_len = 0;
Runnable handleReleasers = null;
try { try {
// Iterate over buffers to populate native iovec array. // Iterate over buffers to populate native iovec array.
int count = offset + length; int count = offset + length;
int i = offset; int i = offset;
while (i < count && iov_len < IOV_MAX) { while (i < count && iov_len < IOV_MAX) {
ByteBuffer buf = bufs[i]; ByteBuffer buf = bufs[i];
var h = acquireScope(buf, async);
if (h != null) {
handleReleasers = LinkedRunnable.of(Releaser.of(h), handleReleasers);
}
int pos = buf.position(); int pos = buf.position();
int lim = buf.limit(); int lim = buf.limit();
assert (pos <= lim); assert (pos <= lim);
@ -170,7 +205,7 @@ public class IOUtil {
pos = shadow.position(); pos = shadow.position();
} }
vec.putBase(iov_len, ((DirectBuffer)buf).address() + pos); vec.putBase(iov_len, bufferAddress(buf) + pos);
vec.putLen(iov_len, rem); vec.putLen(iov_len, rem);
iov_len++; iov_len++;
} }
@ -203,6 +238,7 @@ public class IOUtil {
return bytesWritten; return bytesWritten;
} finally { } finally {
releaseScopes(handleReleasers);
// if an error occurred then clear refs to buffers and return any shadow // if an error occurred then clear refs to buffers and return any shadow
// buffers to cache // buffers to cache
if (!completed) { if (!completed) {
@ -220,17 +256,32 @@ public class IOUtil {
NativeDispatcher nd) NativeDispatcher nd)
throws IOException throws IOException
{ {
return read(fd, dst, position, false, -1, nd); return read(fd, dst, position, false, false, -1, nd);
}
static int read(FileDescriptor fd, ByteBuffer dst, long position,
boolean async, NativeDispatcher nd)
throws IOException
{
return read(fd, dst, position, false, async, -1, nd);
} }
static int read(FileDescriptor fd, ByteBuffer dst, long position, static int read(FileDescriptor fd, ByteBuffer dst, long position,
boolean directIO, int alignment, NativeDispatcher nd) boolean directIO, int alignment, NativeDispatcher nd)
throws IOException throws IOException
{
return read(fd, dst, position, directIO, false, alignment, nd);
}
static int read(FileDescriptor fd, ByteBuffer dst, long position,
boolean directIO, boolean async,
int alignment, NativeDispatcher nd)
throws IOException
{ {
if (dst.isReadOnly()) if (dst.isReadOnly())
throw new IllegalArgumentException("Read-only buffer"); throw new IllegalArgumentException("Read-only buffer");
if (dst instanceof DirectBuffer) if (dst instanceof DirectBuffer)
return readIntoNativeBuffer(fd, dst, position, directIO, alignment, nd); return readIntoNativeBuffer(fd, dst, position, directIO, async, alignment, nd);
// Substitute a native buffer // Substitute a native buffer
ByteBuffer bb; ByteBuffer bb;
@ -242,7 +293,7 @@ public class IOUtil {
bb = Util.getTemporaryDirectBuffer(rem); bb = Util.getTemporaryDirectBuffer(rem);
} }
try { try {
int n = readIntoNativeBuffer(fd, bb, position, directIO, alignment,nd); int n = readIntoNativeBuffer(fd, bb, position, directIO, async, alignment, nd);
bb.flip(); bb.flip();
if (n > 0) if (n > 0)
dst.put(bb); dst.put(bb);
@ -254,7 +305,8 @@ public class IOUtil {
private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb, private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
long position, boolean directIO, long position, boolean directIO,
int alignment, NativeDispatcher nd) boolean async, int alignment,
NativeDispatcher nd)
throws IOException throws IOException
{ {
int pos = bb.position(); int pos = bb.position();
@ -270,10 +322,15 @@ public class IOUtil {
if (rem == 0) if (rem == 0)
return 0; return 0;
int n = 0; int n = 0;
var handle = acquireScope(bb, async);
try {
if (position != -1) { if (position != -1) {
n = nd.pread(fd, ((DirectBuffer)bb).address() + pos, rem, position); n = nd.pread(fd, bufferAddress(bb) + pos, rem, position);
} else { } else {
n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem); n = nd.read(fd, bufferAddress(bb) + pos, rem);
}
} finally {
releaseScope(handle);
} }
if (n > 0) if (n > 0)
bb.position(pos + n); bb.position(pos + n);
@ -283,26 +340,43 @@ public class IOUtil {
static long read(FileDescriptor fd, ByteBuffer[] bufs, NativeDispatcher nd) static long read(FileDescriptor fd, ByteBuffer[] bufs, NativeDispatcher nd)
throws IOException throws IOException
{ {
return read(fd, bufs, 0, bufs.length, false, -1, nd); return read(fd, bufs, 0, bufs.length, false, false, -1, nd);
}
static long read(FileDescriptor fd, ByteBuffer[] bufs, boolean async,
NativeDispatcher nd)
throws IOException
{
return read(fd, bufs, 0, bufs.length, false, async, -1, nd);
} }
static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
NativeDispatcher nd) NativeDispatcher nd)
throws IOException throws IOException
{ {
return read(fd, bufs, offset, length, false, -1, nd); return read(fd, bufs, offset, length, false, false, -1, nd);
} }
static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, int alignment, NativeDispatcher nd) boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
return read(fd, bufs, offset, length, directIO, false, alignment, nd);
}
static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, boolean async,
int alignment, NativeDispatcher nd)
throws IOException throws IOException
{ {
IOVecWrapper vec = IOVecWrapper.get(length); IOVecWrapper vec = IOVecWrapper.get(length);
boolean completed = false; boolean completed = false;
int iov_len = 0; int iov_len = 0;
Runnable handleReleasers = null;
try { try {
// Iterate over buffers to populate native iovec array. // Iterate over buffers to populate native iovec array.
int count = offset + length; int count = offset + length;
int i = offset; int i = offset;
@ -310,6 +384,10 @@ public class IOUtil {
ByteBuffer buf = bufs[i]; ByteBuffer buf = bufs[i];
if (buf.isReadOnly()) if (buf.isReadOnly())
throw new IllegalArgumentException("Read-only buffer"); throw new IllegalArgumentException("Read-only buffer");
var h = acquireScope(buf, async);
if (h != null) {
handleReleasers = LinkedRunnable.of(Releaser.of(h), handleReleasers);
}
int pos = buf.position(); int pos = buf.position();
int lim = buf.limit(); int lim = buf.limit();
assert (pos <= lim); assert (pos <= lim);
@ -334,7 +412,7 @@ public class IOUtil {
pos = shadow.position(); pos = shadow.position();
} }
vec.putBase(iov_len, ((DirectBuffer)buf).address() + pos); vec.putBase(iov_len, bufferAddress(buf) + pos);
vec.putLen(iov_len, rem); vec.putLen(iov_len, rem);
iov_len++; iov_len++;
} }
@ -371,6 +449,7 @@ public class IOUtil {
return bytesRead; return bytesRead;
} finally { } finally {
releaseScopes(handleReleasers);
// if an error occurred then clear refs to buffers and return any shadow // if an error occurred then clear refs to buffers and return any shadow
// buffers to cache // buffers to cache
if (!completed) { if (!completed) {
@ -384,6 +463,83 @@ public class IOUtil {
} }
} }
private static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess();
static Scope.Handle acquireScope(ByteBuffer bb, boolean async) {
return NIO_ACCESS.acquireScope(bb, async);
}
private static void releaseScope(Scope.Handle handle) {
if (handle == null)
return;
try {
handle.scope().release(handle);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
static Runnable acquireScopes(ByteBuffer[] buffers) {
return acquireScopes(null, buffers);
}
static Runnable acquireScopes(ByteBuffer buf, ByteBuffer[] buffers) {
if (buffers == null) {
assert buf != null;
return IOUtil.Releaser.ofNullable(IOUtil.acquireScope(buf, true));
} else {
assert buf == null;
Runnable handleReleasers = null;
for (var b : buffers) {
var h = IOUtil.acquireScope(b, true);
if (h != null) {
handleReleasers = IOUtil.LinkedRunnable.of(IOUtil.Releaser.of(h), handleReleasers);
}
}
return handleReleasers;
}
}
static void releaseScopes(Runnable releasers) {
if (releasers != null)
releasers.run();
}
static record LinkedRunnable(Runnable node, Runnable next)
implements Runnable
{
LinkedRunnable {
Objects.requireNonNull(node);
}
@Override
public void run() {
try {
node.run();
} finally {
if (next != null)
next.run();
}
}
static LinkedRunnable of(Runnable first, Runnable second) {
return new LinkedRunnable(first, second);
}
}
static record Releaser(Scope.Handle handle) implements Runnable {
Releaser { Objects.requireNonNull(handle) ; }
@Override public void run() { releaseScope(handle); }
static Runnable of(Scope.Handle handle) { return new Releaser(handle); }
static Runnable ofNullable(Scope.Handle handle) {
if (handle == null)
return () -> { };
return new Releaser(handle);
}
}
static long bufferAddress(ByteBuffer buf) {
return NIO_ACCESS.getBufferAddress(buf);
}
public static FileDescriptor newFD(int i) { public static FileDescriptor newFD(int i) {
FileDescriptor fd = new FileDescriptor(); FileDescriptor fd = new FileDescriptor();
setfdVal(fd, i); setfdVal(fd, i);

View file

@ -599,6 +599,7 @@ IsModuleOption(const char* name) {
JLI_StrCmp(name, "-p") == 0 || JLI_StrCmp(name, "-p") == 0 ||
JLI_StrCmp(name, "--upgrade-module-path") == 0 || JLI_StrCmp(name, "--upgrade-module-path") == 0 ||
JLI_StrCmp(name, "--add-modules") == 0 || JLI_StrCmp(name, "--add-modules") == 0 ||
JLI_StrCmp(name, "--enable-native-access") == 0 ||
JLI_StrCmp(name, "--limit-modules") == 0 || JLI_StrCmp(name, "--limit-modules") == 0 ||
JLI_StrCmp(name, "--add-exports") == 0 || JLI_StrCmp(name, "--add-exports") == 0 ||
JLI_StrCmp(name, "--add-opens") == 0 || JLI_StrCmp(name, "--add-opens") == 0 ||
@ -611,6 +612,7 @@ IsLongFormModuleOption(const char* name) {
return JLI_StrCCmp(name, "--module-path=") == 0 || return JLI_StrCCmp(name, "--module-path=") == 0 ||
JLI_StrCCmp(name, "--upgrade-module-path=") == 0 || JLI_StrCCmp(name, "--upgrade-module-path=") == 0 ||
JLI_StrCCmp(name, "--add-modules=") == 0 || JLI_StrCCmp(name, "--add-modules=") == 0 ||
JLI_StrCCmp(name, "--enable-native-access=") == 0 ||
JLI_StrCCmp(name, "--limit-modules=") == 0 || JLI_StrCCmp(name, "--limit-modules=") == 0 ||
JLI_StrCCmp(name, "--add-exports=") == 0 || JLI_StrCCmp(name, "--add-exports=") == 0 ||
JLI_StrCCmp(name, "--add-reads=") == 0 || JLI_StrCCmp(name, "--add-reads=") == 0 ||

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -76,6 +76,7 @@ class UnixAsynchronousSocketChannelImpl
private boolean isScatteringRead; private boolean isScatteringRead;
private ByteBuffer readBuffer; private ByteBuffer readBuffer;
private ByteBuffer[] readBuffers; private ByteBuffer[] readBuffers;
private Runnable readScopeHandleReleasers;
private CompletionHandler<Number,Object> readHandler; private CompletionHandler<Number,Object> readHandler;
private Object readAttachment; private Object readAttachment;
private PendingFuture<Number,Object> readFuture; private PendingFuture<Number,Object> readFuture;
@ -86,6 +87,7 @@ class UnixAsynchronousSocketChannelImpl
private boolean isGatheringWrite; private boolean isGatheringWrite;
private ByteBuffer writeBuffer; private ByteBuffer writeBuffer;
private ByteBuffer[] writeBuffers; private ByteBuffer[] writeBuffers;
private Runnable writeScopeHandleReleasers;
private CompletionHandler<Number,Object> writeHandler; private CompletionHandler<Number,Object> writeHandler;
private Object writeAttachment; private Object writeAttachment;
private PendingFuture<Number,Object> writeFuture; private PendingFuture<Number,Object> writeFuture;
@ -392,9 +394,9 @@ class UnixAsynchronousSocketChannelImpl
begin(); begin();
if (scattering) { if (scattering) {
n = (int)IOUtil.read(fd, readBuffers, nd); n = (int)IOUtil.read(fd, readBuffers, true, nd);
} else { } else {
n = IOUtil.read(fd, readBuffer, -1, nd); n = IOUtil.read(fd, readBuffer, -1, true, nd);
} }
if (n == IOStatus.UNAVAILABLE) { if (n == IOStatus.UNAVAILABLE) {
// spurious wakeup, is this possible? // spurious wakeup, is this possible?
@ -409,6 +411,7 @@ class UnixAsynchronousSocketChannelImpl
this.readBuffers = null; this.readBuffers = null;
this.readAttachment = null; this.readAttachment = null;
this.readHandler = null; this.readHandler = null;
IOUtil.releaseScopes(readScopeHandleReleasers);
// allow another read to be initiated // allow another read to be initiated
enableReading(); enableReading();
@ -516,9 +519,9 @@ class UnixAsynchronousSocketChannelImpl
if (attemptRead) { if (attemptRead) {
if (isScatteringRead) { if (isScatteringRead) {
n = (int)IOUtil.read(fd, dsts, nd); n = (int)IOUtil.read(fd, dsts, true, nd);
} else { } else {
n = IOUtil.read(fd, dst, -1, nd); n = IOUtil.read(fd, dst, -1, true, nd);
} }
} }
@ -526,6 +529,7 @@ class UnixAsynchronousSocketChannelImpl
PendingFuture<V,A> result = null; PendingFuture<V,A> result = null;
synchronized (updateLock) { synchronized (updateLock) {
this.isScatteringRead = isScatteringRead; this.isScatteringRead = isScatteringRead;
this.readScopeHandleReleasers = IOUtil.acquireScopes(dst, dsts);
this.readBuffer = dst; this.readBuffer = dst;
this.readBuffers = dsts; this.readBuffers = dsts;
if (handler == null) { if (handler == null) {
@ -592,9 +596,9 @@ class UnixAsynchronousSocketChannelImpl
begin(); begin();
if (gathering) { if (gathering) {
n = (int)IOUtil.write(fd, writeBuffers, nd); n = (int)IOUtil.write(fd, writeBuffers, true, nd);
} else { } else {
n = IOUtil.write(fd, writeBuffer, -1, nd); n = IOUtil.write(fd, writeBuffer, -1, true, nd);
} }
if (n == IOStatus.UNAVAILABLE) { if (n == IOStatus.UNAVAILABLE) {
// spurious wakeup, is this possible? // spurious wakeup, is this possible?
@ -609,6 +613,7 @@ class UnixAsynchronousSocketChannelImpl
this.writeBuffers = null; this.writeBuffers = null;
this.writeAttachment = null; this.writeAttachment = null;
this.writeHandler = null; this.writeHandler = null;
IOUtil.releaseScopes(writeScopeHandleReleasers);
// allow another write to be initiated // allow another write to be initiated
enableWriting(); enableWriting();
@ -702,9 +707,9 @@ class UnixAsynchronousSocketChannelImpl
if (attemptWrite) { if (attemptWrite) {
if (isGatheringWrite) { if (isGatheringWrite) {
n = (int)IOUtil.write(fd, srcs, nd); n = (int)IOUtil.write(fd, srcs, true, nd);
} else { } else {
n = IOUtil.write(fd, src, -1, nd); n = IOUtil.write(fd, src, -1, true, nd);
} }
} }
@ -712,6 +717,7 @@ class UnixAsynchronousSocketChannelImpl
PendingFuture<V,A> result = null; PendingFuture<V,A> result = null;
synchronized (updateLock) { synchronized (updateLock) {
this.isGatheringWrite = isGatheringWrite; this.isGatheringWrite = isGatheringWrite;
this.writeScopeHandleReleasers = IOUtil.acquireScopes(src, srcs);
this.writeBuffer = src; this.writeBuffer = src;
this.writeBuffers = srcs; this.writeBuffers = srcs;
if (handler == null) { if (handler == null) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -400,6 +400,7 @@ class WindowsAsynchronousSocketChannelImpl
// set by run method // set by run method
private ByteBuffer[] shadow; private ByteBuffer[] shadow;
private Runnable scopeHandleReleasers;
ReadTask(ByteBuffer[] bufs, ReadTask(ByteBuffer[] bufs,
boolean scatteringRead, boolean scatteringRead,
@ -416,6 +417,7 @@ class WindowsAsynchronousSocketChannelImpl
* it substitutes non-direct buffers with direct buffers. * it substitutes non-direct buffers with direct buffers.
*/ */
void prepareBuffers() { void prepareBuffers() {
scopeHandleReleasers = IOUtil.acquireScopes(bufs);
shadow = new ByteBuffer[numBufs]; shadow = new ByteBuffer[numBufs];
long address = readBufferArray; long address = readBufferArray;
for (int i=0; i<numBufs; i++) { for (int i=0; i<numBufs; i++) {
@ -429,10 +431,10 @@ class WindowsAsynchronousSocketChannelImpl
// substitute with direct buffer // substitute with direct buffer
ByteBuffer bb = Util.getTemporaryDirectBuffer(rem); ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
shadow[i] = bb; shadow[i] = bb;
a = ((DirectBuffer)bb).address(); a = IOUtil.bufferAddress(bb);
} else { } else {
shadow[i] = dst; shadow[i] = dst;
a = ((DirectBuffer)dst).address() + pos; a = IOUtil.bufferAddress(dst) + pos;
} }
unsafe.putAddress(address + OFFSETOF_BUF, a); unsafe.putAddress(address + OFFSETOF_BUF, a);
unsafe.putInt(address + OFFSETOF_LEN, rem); unsafe.putInt(address + OFFSETOF_LEN, rem);
@ -490,6 +492,7 @@ class WindowsAsynchronousSocketChannelImpl
Util.releaseTemporaryDirectBuffer(shadow[i]); Util.releaseTemporaryDirectBuffer(shadow[i]);
} }
} }
IOUtil.releaseScopes(scopeHandleReleasers);
} }
@Override @Override
@ -671,6 +674,7 @@ class WindowsAsynchronousSocketChannelImpl
// set by run method // set by run method
private ByteBuffer[] shadow; private ByteBuffer[] shadow;
private Runnable scopeHandleReleasers;
WriteTask(ByteBuffer[] bufs, WriteTask(ByteBuffer[] bufs,
boolean gatheringWrite, boolean gatheringWrite,
@ -687,6 +691,7 @@ class WindowsAsynchronousSocketChannelImpl
* it substitutes non-direct buffers with direct buffers. * it substitutes non-direct buffers with direct buffers.
*/ */
void prepareBuffers() { void prepareBuffers() {
scopeHandleReleasers = IOUtil.acquireScopes(bufs);
shadow = new ByteBuffer[numBufs]; shadow = new ByteBuffer[numBufs];
long address = writeBufferArray; long address = writeBufferArray;
for (int i=0; i<numBufs; i++) { for (int i=0; i<numBufs; i++) {
@ -703,10 +708,10 @@ class WindowsAsynchronousSocketChannelImpl
bb.flip(); bb.flip();
src.position(pos); // leave heap buffer untouched for now src.position(pos); // leave heap buffer untouched for now
shadow[i] = bb; shadow[i] = bb;
a = ((DirectBuffer)bb).address(); a = IOUtil.bufferAddress(bb);
} else { } else {
shadow[i] = src; shadow[i] = src;
a = ((DirectBuffer)src).address() + pos; a = IOUtil.bufferAddress(src) + pos;
} }
unsafe.putAddress(address + OFFSETOF_BUF, a); unsafe.putAddress(address + OFFSETOF_BUF, a);
unsafe.putInt(address + OFFSETOF_LEN, rem); unsafe.putInt(address + OFFSETOF_LEN, rem);
@ -754,6 +759,7 @@ class WindowsAsynchronousSocketChannelImpl
Util.releaseTemporaryDirectBuffer(shadow[i]); Util.releaseTemporaryDirectBuffer(shadow[i]);
} }
} }
IOUtil.releaseScopes(scopeHandleReleasers);
} }
@Override @Override

View file

@ -48,7 +48,7 @@ import static java.lang.constant.ConstantDescs.BSM_INVOKE;
import static java.lang.constant.ConstantDescs.CD_String; import static java.lang.constant.ConstantDescs.CD_String;
import static java.lang.constant.ConstantDescs.CD_long; import static java.lang.constant.ConstantDescs.CD_long;
abstract class AbstractLayout implements MemoryLayout { abstract non-sealed class AbstractLayout implements MemoryLayout {
private final OptionalLong size; private final OptionalLong size;
final long alignment; final long alignment;
@ -214,22 +214,22 @@ abstract class AbstractLayout implements MemoryLayout {
static final ConstantDesc LITTLE_ENDIAN = DynamicConstantDesc.ofNamed(BSM_GET_STATIC_FINAL, "LITTLE_ENDIAN", CD_BYTEORDER, CD_BYTEORDER); static final ConstantDesc LITTLE_ENDIAN = DynamicConstantDesc.ofNamed(BSM_GET_STATIC_FINAL, "LITTLE_ENDIAN", CD_BYTEORDER, CD_BYTEORDER);
static final MethodHandleDesc MH_PADDING = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofPaddingBits", static final MethodHandleDesc MH_PADDING = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "paddingLayout",
MethodTypeDesc.of(CD_MEMORY_LAYOUT, CD_long)); MethodTypeDesc.of(CD_MEMORY_LAYOUT, CD_long));
static final MethodHandleDesc MH_VALUE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofValueBits", static final MethodHandleDesc MH_VALUE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "valueLayout",
MethodTypeDesc.of(CD_VALUE_LAYOUT, CD_long, CD_BYTEORDER)); MethodTypeDesc.of(CD_VALUE_LAYOUT, CD_long, CD_BYTEORDER));
static final MethodHandleDesc MH_SIZED_SEQUENCE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofSequence", static final MethodHandleDesc MH_SIZED_SEQUENCE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "sequenceLayout",
MethodTypeDesc.of(CD_SEQUENCE_LAYOUT, CD_long, CD_MEMORY_LAYOUT)); MethodTypeDesc.of(CD_SEQUENCE_LAYOUT, CD_long, CD_MEMORY_LAYOUT));
static final MethodHandleDesc MH_UNSIZED_SEQUENCE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofSequence", static final MethodHandleDesc MH_UNSIZED_SEQUENCE = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "sequenceLayout",
MethodTypeDesc.of(CD_SEQUENCE_LAYOUT, CD_MEMORY_LAYOUT)); MethodTypeDesc.of(CD_SEQUENCE_LAYOUT, CD_MEMORY_LAYOUT));
static final MethodHandleDesc MH_STRUCT = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofStruct", static final MethodHandleDesc MH_STRUCT = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "structLayout",
MethodTypeDesc.of(CD_GROUP_LAYOUT, CD_MEMORY_LAYOUT.arrayType())); MethodTypeDesc.of(CD_GROUP_LAYOUT, CD_MEMORY_LAYOUT.arrayType()));
static final MethodHandleDesc MH_UNION = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "ofUnion", static final MethodHandleDesc MH_UNION = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, CD_MEMORY_LAYOUT, "unionLayout",
MethodTypeDesc.of(CD_GROUP_LAYOUT, CD_MEMORY_LAYOUT.arrayType())); MethodTypeDesc.of(CD_GROUP_LAYOUT, CD_MEMORY_LAYOUT.arrayType()));
static final MethodHandleDesc MH_VOID_FUNCTION = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, CD_FUNCTION_DESC, "ofVoid", static final MethodHandleDesc MH_VOID_FUNCTION = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, CD_FUNCTION_DESC, "ofVoid",

View file

@ -28,12 +28,7 @@ package jdk.incubator.foreign;
/** /**
* Represents a type which is <em>addressable</em>. An addressable type is one which can be projected down to * Represents a type which is <em>addressable</em>. An addressable type is one which can be projected down to
* a memory address instance (see {@link #address()}). Examples of addressable types are {@link MemorySegment}, * a memory address instance (see {@link #address()}). Examples of addressable types are {@link MemorySegment},
* {@link MemoryAddress}, {@link LibraryLookup.Symbol} and {@link CLinker.VaList}. * {@link MemoryAddress} and {@link CLinker.VaList}.
*
* @apiNote In the future, if the Java language permits, {@link Addressable}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types, such as {@link MemorySegment}, {@link MemoryAddress}, {@link LibraryLookup.Symbol}
* and {@link CLinker.VaList}.
* *
* @implSpec * @implSpec
* Implementations of this interface are <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>. * Implementations of this interface are <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,10 +25,15 @@
*/ */
package jdk.incubator.foreign; package jdk.incubator.foreign;
import jdk.internal.foreign.AbstractCLinker;
import jdk.internal.foreign.NativeMemorySegmentImpl; import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.PlatformLayouts; import jdk.internal.foreign.PlatformLayouts;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.abi.aarch64.AArch64VaList;
import jdk.internal.foreign.abi.x64.sysv.SysVVaList;
import jdk.internal.foreign.abi.x64.windows.WinVaList;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import java.lang.constant.Constable; import java.lang.constant.Constable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
@ -36,7 +41,6 @@ import java.lang.invoke.MethodType;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream;
import static jdk.internal.foreign.PlatformLayouts.*; import static jdk.internal.foreign.PlatformLayouts.*;
@ -65,7 +69,8 @@ import static jdk.internal.foreign.PlatformLayouts.*;
* carrier type can be used to match the native {@code va_list} type. * carrier type can be used to match the native {@code va_list} type.
* <p> * <p>
* For the linking process to be successful, some requirements must be satisfied; if {@code M} and {@code F} are * For the linking process to be successful, some requirements must be satisfied; if {@code M} and {@code F} are
* the method type and the function descriptor, respectively, used during the linking process, then it must be that: * the method type (obtained after dropping any prefix arguments) and the function descriptor, respectively,
* used during the linking process, then it must be that:
* <ul> * <ul>
* <li>The arity of {@code M} is the same as that of {@code F};</li> * <li>The arity of {@code M} is the same as that of {@code F};</li>
* <li>If the return type of {@code M} is {@code void}, then {@code F} should have no return layout * <li>If the return type of {@code M} is {@code void}, then {@code F} should have no return layout
@ -100,33 +105,37 @@ import static jdk.internal.foreign.PlatformLayouts.*;
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null} * <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p> * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
* *
* @apiNote In the future, if the Java language permits, {@link CLinker}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
* @implSpec * @implSpec
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>. * Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
*/ */
public interface CLinker { public sealed interface CLinker permits AbstractCLinker {
/** /**
* Returns the C linker for the current platform. * Returns the C linker for the current platform.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
*
* @return a linker for this system. * @return a linker for this system.
* @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either * @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}). * {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
@CallerSensitive
static CLinker getInstance() { static CLinker getInstance() {
Utils.checkRestrictedAccess("CLinker.getInstance"); Reflection.ensureNativeAccess(Reflection.getCallerClass());
return SharedUtils.getSystemLinker(); return SharedUtils.getSystemLinker();
} }
/** /**
* Obtain a foreign method handle, with given type, which can be used to call a * Obtains a foreign method handle, with the given type and featuring the given function descriptor,
* target foreign function at a given address and featuring a given function descriptor. * which can be used to call a target foreign function at the given address.
* <p>
* If the provided method type's return type is {@code MemorySegment}, then the resulting method handle features
* an additional prefix parameter, of type {@link SegmentAllocator}, which will be used by the linker runtime
* to allocate structs returned by-value.
* *
* @see LibraryLookup#lookup(String) * @see LibraryLookup#lookup(String)
* *
@ -139,20 +148,58 @@ public interface CLinker {
MethodHandle downcallHandle(Addressable symbol, MethodType type, FunctionDescriptor function); MethodHandle downcallHandle(Addressable symbol, MethodType type, FunctionDescriptor function);
/** /**
* Allocates a native segment whose base address (see {@link MemorySegment#address}) can be * Obtain a foreign method handle, with the given type and featuring the given function descriptor,
* passed to other foreign functions (as a function pointer); calling such a function pointer * which can be used to call a target foreign function at the given address.
* from native code will result in the execution of the provided method handle. * <p>
* If the provided method type's return type is {@code MemorySegment}, then the provided allocator will be used by
* the linker runtime to allocate structs returned by-value.
* *
* <p>The returned segment is <a href=MemorySegment.html#thread-confinement>shared</a>, and it only features * @see LibraryLookup#lookup(String)
* the {@link MemorySegment#CLOSE} access mode. When the returned segment is closed, *
* the corresponding native stub will be deallocated.</p> * @param symbol downcall symbol.
* @param allocator the segment allocator.
* @param type the method type.
* @param function the function descriptor.
* @return the downcall method handle.
* @throws IllegalArgumentException in the case of a method type and function descriptor mismatch.
*/
MethodHandle downcallHandle(Addressable symbol, SegmentAllocator allocator, MethodType type, FunctionDescriptor function);
/**
* Obtains a foreign method handle, with the given type and featuring the given function descriptor, which can be
* used to call a target foreign function at an address.
* The resulting method handle features a prefix parameter (as the first parameter) corresponding to the address, of
* type {@link Addressable}.
* <p>
* If the provided method type's return type is {@code MemorySegment}, then the resulting method handle features an
* additional prefix parameter (inserted immediately after the address parameter), of type {@link SegmentAllocator}),
* which will be used by the linker runtime to allocate structs returned by-value.
*
* @see LibraryLookup#lookup(String)
*
* @param type the method type.
* @param function the function descriptor.
* @return the downcall method handle.
* @throws IllegalArgumentException in the case of a method type and function descriptor mismatch.
*/
MethodHandle downcallHandle(MethodType type, FunctionDescriptor function);
/**
* Allocates a native stub with given scope which can be passed to other foreign functions (as a function pointer);
* calling such a function pointer from native code will result in the execution of the provided method handle.
*
* <p>The returned memory address is associated with the provided scope. When such scope is closed,
* the corresponding native stub will be deallocated.
* *
* @param target the target method handle. * @param target the target method handle.
* @param function the function descriptor. * @param function the function descriptor.
* @param scope the upcall stub scope.
* @return the native stub segment. * @return the native stub segment.
* @throws IllegalArgumentException if the target's method type and the function descriptor mismatch. * @throws IllegalArgumentException if the target's method type and the function descriptor mismatch.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/ */
MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function); MemoryAddress upcallStub(MethodHandle target, FunctionDescriptor function, ResourceScope scope);
/** /**
* The layout for the {@code char} C type * The layout for the {@code char} C type
@ -205,8 +252,8 @@ public interface CLinker {
} }
/** /**
* Converts a Java string into a null-terminated C string, using the * Converts a Java string into a null-terminated C string, using the platform's default charset,
* platform's default charset, storing the result into a new native memory segment. * storing the result into a native memory segment allocated using the provided allocator.
* <p> * <p>
* This method always replaces malformed-input and unmappable-character * This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The * sequences with this charset's default replacement byte array. The
@ -214,35 +261,18 @@ public interface CLinker {
* control over the encoding process is required. * control over the encoding process is required.
* *
* @param str the Java string to be converted into a C string. * @param str the Java string to be converted into a C string.
* @param allocator the allocator to be used for the native segment allocation.
* @return a new native memory segment containing the converted C string. * @return a new native memory segment containing the converted C string.
*/ */
static MemorySegment toCString(String str) { static MemorySegment toCString(String str, SegmentAllocator allocator) {
Objects.requireNonNull(str); Objects.requireNonNull(str);
return toCString(str.getBytes()); Objects.requireNonNull(allocator);
} return toCString(str.getBytes(), allocator);
/**
* Converts a Java string into a null-terminated C string, using the given {@link java.nio.charset.Charset charset},
* storing the result into a new native memory segment.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
* {@link java.nio.charset.CharsetEncoder} class should be used when more
* control over the encoding process is required.
*
* @param str the Java string to be converted into a C string.
* @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string.
* @return a new native memory segment containing the converted C string.
*/
static MemorySegment toCString(String str, Charset charset) {
Objects.requireNonNull(str);
Objects.requireNonNull(charset);
return toCString(str.getBytes(charset));
} }
/** /**
* Converts a Java string into a null-terminated C string, using the platform's default charset, * Converts a Java string into a null-terminated C string, using the platform's default charset,
* storing the result into a native memory segment allocated using the provided scope. * storing the result into a native memory segment associated with the provided resource scope.
* <p> * <p>
* This method always replaces malformed-input and unmappable-character * This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The * sequences with this charset's default replacement byte array. The
@ -250,18 +280,18 @@ public interface CLinker {
* control over the encoding process is required. * control over the encoding process is required.
* *
* @param str the Java string to be converted into a C string. * @param str the Java string to be converted into a C string.
* @param scope the scope to be used for the native segment allocation. * @param scope the resource scope to be associated with the returned segment.
* @return a new native memory segment containing the converted C string. * @return a new native memory segment containing the converted C string.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/ */
static MemorySegment toCString(String str, NativeScope scope) { static MemorySegment toCString(String str, ResourceScope scope) {
Objects.requireNonNull(str); return toCString(str, SegmentAllocator.ofScope(scope));
Objects.requireNonNull(scope);
return toCString(str.getBytes(), scope);
} }
/** /**
* Converts a Java string into a null-terminated C string, using the given {@link java.nio.charset.Charset charset}, * Converts a Java string into a null-terminated C string, using the given {@linkplain java.nio.charset.Charset charset},
* storing the result into a new native memory segment native memory segment allocated using the provided scope. * storing the result into a new native memory segment native memory segment allocated using the provided allocator.
* <p> * <p>
* This method always replaces malformed-input and unmappable-character * This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The * sequences with this charset's default replacement byte array. The
@ -270,14 +300,34 @@ public interface CLinker {
* *
* @param str the Java string to be converted into a C string. * @param str the Java string to be converted into a C string.
* @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string. * @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string.
* @param scope the scope to be used for the native segment allocation. * @param allocator the allocator to be used for the native segment allocation.
* @return a new native memory segment containing the converted C string. * @return a new native memory segment containing the converted C string.
*/ */
static MemorySegment toCString(String str, Charset charset, NativeScope scope) { static MemorySegment toCString(String str, Charset charset, SegmentAllocator allocator) {
Objects.requireNonNull(str); Objects.requireNonNull(str);
Objects.requireNonNull(charset); Objects.requireNonNull(charset);
Objects.requireNonNull(scope); Objects.requireNonNull(allocator);
return toCString(str.getBytes(charset), scope); return toCString(str.getBytes(charset), allocator);
}
/**
* Converts a Java string into a null-terminated C string, using the given {@linkplain java.nio.charset.Charset charset},
* storing the result into a native memory segment associated with the provided resource scope.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
* {@link java.nio.charset.CharsetEncoder} class should be used when more
* control over the encoding process is required.
*
* @param str the Java string to be converted into a C string.
* @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string.
* @param scope the resource scope to be associated with the returned segment.
* @return a new native memory segment containing the converted C string.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/
static MemorySegment toCString(String str, Charset charset, ResourceScope scope) {
return toCString(str, charset, SegmentAllocator.ofScope(scope));
} }
/** /**
@ -288,37 +338,49 @@ public interface CLinker {
* java.nio.charset.CharsetDecoder} class should be used when more control * java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required. * over the decoding process is required.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
*
* @param addr the address at which the string is stored. * @param addr the address at which the string is stored.
* @return a Java string with the contents of the null-terminated C string at given address. * @return a Java string with the contents of the null-terminated C string at given address.
* @throws IllegalArgumentException if the size of the native string is greater than the largest string supported by the platform. * @throws IllegalArgumentException if the size of the native string is greater than the largest string supported by the platform.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static String toJavaStringRestricted(MemoryAddress addr) { @CallerSensitive
Utils.checkRestrictedAccess("CLinker.toJavaStringRestricted"); static String toJavaString(MemoryAddress addr) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(addr); Objects.requireNonNull(addr);
return SharedUtils.toJavaStringInternal(NativeMemorySegmentImpl.EVERYTHING, addr.toRawLongValue(), Charset.defaultCharset()); return SharedUtils.toJavaStringInternal(NativeMemorySegmentImpl.EVERYTHING, addr.toRawLongValue(), Charset.defaultCharset());
} }
/** /**
* Converts a null-terminated C string stored at given address into a Java string, using the given {@link java.nio.charset.Charset charset}. * Converts a null-terminated C string stored at given address into a Java string, using the given {@linkplain java.nio.charset.Charset charset}.
* <p> * <p>
* This method always replaces malformed-input and unmappable-character * This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link * sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control * java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required. * over the decoding process is required.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
*
* @param addr the address at which the string is stored. * @param addr the address at which the string is stored.
* @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the Java string. * @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the Java string.
* @return a Java string with the contents of the null-terminated C string at given address. * @return a Java string with the contents of the null-terminated C string at given address.
* @throws IllegalArgumentException if the size of the native string is greater than the largest string supported by the platform. * @throws IllegalArgumentException if the size of the native string is greater than the largest string supported by the platform.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static String toJavaStringRestricted(MemoryAddress addr, Charset charset) { @CallerSensitive
Utils.checkRestrictedAccess("CLinker.toJavaStringRestricted"); static String toJavaString(MemoryAddress addr, Charset charset) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(addr); Objects.requireNonNull(addr);
Objects.requireNonNull(charset); Objects.requireNonNull(charset);
return SharedUtils.toJavaStringInternal(NativeMemorySegmentImpl.EVERYTHING, addr.toRawLongValue(), charset); return SharedUtils.toJavaStringInternal(NativeMemorySegmentImpl.EVERYTHING, addr.toRawLongValue(), charset);
@ -343,7 +405,7 @@ public interface CLinker {
} }
/** /**
* Converts a null-terminated C string stored at given address into a Java string, using the given {@link java.nio.charset.Charset charset}. * Converts a null-terminated C string stored at given address into a Java string, using the given {@linkplain java.nio.charset.Charset charset}.
* <p> * <p>
* This method always replaces malformed-input and unmappable-character * This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link * sequences with this charset's default replacement string. The {@link
@ -368,14 +430,8 @@ public interface CLinker {
MemoryAccess.setByteAtOffset(addr, bytes.length, (byte)0); MemoryAccess.setByteAtOffset(addr, bytes.length, (byte)0);
} }
private static MemorySegment toCString(byte[] bytes) { private static MemorySegment toCString(byte[] bytes, SegmentAllocator allocator) {
MemorySegment segment = MemorySegment.allocateNative(bytes.length + 1, 1L); MemorySegment addr = allocator.allocate(bytes.length + 1, 1L);
copy(segment, bytes);
return segment;
}
private static MemorySegment toCString(byte[] bytes, NativeScope scope) {
MemorySegment addr = scope.allocate(bytes.length + 1, 1L);
copy(addr, bytes); copy(addr, bytes);
return addr; return addr;
} }
@ -383,16 +439,21 @@ public interface CLinker {
/** /**
* Allocates memory of given size using malloc. * Allocates memory of given size using malloc.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
* *
* @param size memory size to be allocated * @param size memory size to be allocated
* @return addr memory address of the allocated memory * @return addr memory address of the allocated memory
* @throws OutOfMemoryError if malloc could not allocate the required amount of native memory. * @throws OutOfMemoryError if malloc could not allocate the required amount of native memory.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static MemoryAddress allocateMemoryRestricted(long size) { @CallerSensitive
Utils.checkRestrictedAccess("CLinker.allocateMemoryRestricted"); static MemoryAddress allocateMemory(long size) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
MemoryAddress addr = SharedUtils.allocateMemoryInternal(size); MemoryAddress addr = SharedUtils.allocateMemoryInternal(size);
if (addr.equals(MemoryAddress.NULL)) { if (addr.equals(MemoryAddress.NULL)) {
throw new OutOfMemoryError(); throw new OutOfMemoryError();
@ -404,14 +465,19 @@ public interface CLinker {
/** /**
* Frees the memory pointed by the given memory address. * Frees the memory pointed by the given memory address.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
* *
* @param addr memory address of the native memory to be freed * @param addr memory address of the native memory to be freed
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static void freeMemoryRestricted(MemoryAddress addr) { @CallerSensitive
Utils.checkRestrictedAccess("CLinker.freeMemoryRestricted"); static void freeMemory(MemoryAddress addr) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(addr); Objects.requireNonNull(addr);
SharedUtils.freeMemoryInternal(addr); SharedUtils.freeMemoryInternal(addr);
} }
@ -431,21 +497,16 @@ public interface CLinker {
* *
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null} * <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p> * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
*
* @apiNote In the future, if the Java language permits, {@link VaList}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
*/ */
interface VaList extends Addressable, AutoCloseable { sealed interface VaList extends Addressable permits WinVaList, SysVVaList, AArch64VaList, SharedUtils.EmptyVaList {
/** /**
* Reads the next value as an {@code int} and advances this va list's position. * Reads the next value as an {@code int} and advances this va list's position.
* *
* @param layout the layout of the value * @param layout the layout of the value
* @return the value read as an {@code int} * @return the value read as an {@code int}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code int} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code int}
*/ */
int vargAsInt(MemoryLayout layout); int vargAsInt(MemoryLayout layout);
@ -455,8 +516,8 @@ public interface CLinker {
* *
* @param layout the layout of the value * @param layout the layout of the value
* @return the value read as an {@code long} * @return the value read as an {@code long}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code long} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code long}
*/ */
long vargAsLong(MemoryLayout layout); long vargAsLong(MemoryLayout layout);
@ -466,8 +527,8 @@ public interface CLinker {
* *
* @param layout the layout of the value * @param layout the layout of the value
* @return the value read as an {@code double} * @return the value read as an {@code double}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code double} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code double}
*/ */
double vargAsDouble(MemoryLayout layout); double vargAsDouble(MemoryLayout layout);
@ -477,8 +538,8 @@ public interface CLinker {
* *
* @param layout the layout of the value * @param layout the layout of the value
* @return the value read as an {@code MemoryAddress} * @return the value read as an {@code MemoryAddress}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemoryAddress} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemoryAddress}
*/ */
MemoryAddress vargAsAddress(MemoryLayout layout); MemoryAddress vargAsAddress(MemoryLayout layout);
@ -486,102 +547,70 @@ public interface CLinker {
/** /**
* Reads the next value as a {@code MemorySegment}, and advances this va list's position. * Reads the next value as a {@code MemorySegment}, and advances this va list's position.
* <p> * <p>
* The memory segment returned by this method will be allocated using * The memory segment returned by this method will be allocated using the given {@link SegmentAllocator}.
* {@link MemorySegment#allocateNative(long, long)}, and will have to be closed separately.
* *
* @param layout the layout of the value * @param layout the layout of the value
* @param allocator the allocator to be used for the native segment allocation
* @return the value read as an {@code MemorySegment} * @return the value read as an {@code MemorySegment}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment}
*/ */
MemorySegment vargAsSegment(MemoryLayout layout); MemorySegment vargAsSegment(MemoryLayout layout, SegmentAllocator allocator);
/** /**
* Reads the next value as a {@code MemorySegment}, and advances this va list's position. * Reads the next value as a {@code MemorySegment}, and advances this va list's position.
* <p> * <p>
* The memory segment returned by this method will be allocated using the given {@code NativeScope}. * The memory segment returned by this method will be associated with the given {@link ResourceScope}.
* *
* @param layout the layout of the value * @param layout the layout of the value
* @param scope the scope to allocate the segment in * @param scope the resource scope to be associated with the returned segment
* @return the value read as an {@code MemorySegment} * @return the value read as an {@code MemorySegment}
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment} * @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment}
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/ */
MemorySegment vargAsSegment(MemoryLayout layout, NativeScope scope); MemorySegment vargAsSegment(MemoryLayout layout, ResourceScope scope);
/** /**
* Skips a number of elements with the given memory layouts, and advances this va list's position. * Skips a number of elements with the given memory layouts, and advances this va list's position.
* *
* @param layouts the layout of the value * @param layouts the layout of the value
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
*/ */
void skip(MemoryLayout... layouts); void skip(MemoryLayout... layouts);
/** /**
* A predicate used to check if the memory associated with the C {@code va_list} modelled * Returns the resource scope associated with this instance.
* by this instance is still valid to use. * @return the resource scope associated with this instance.
*
* @return true, if the memory associated with the C {@code va_list} modelled by this instance is still valid
* @see #close()
*/ */
boolean isAlive(); ResourceScope scope();
/**
* Releases the underlying C {@code va_list} modelled by this instance, and any native memory that is attached
* to this va list that holds its elements (see {@link VaList#make(Consumer)}).
* <p>
* After calling this method, {@link #isAlive()} will return {@code false} and further attempts to read values
* from this va list will result in an exception.
*
* @see #isAlive()
*/
void close();
/** /**
* Copies this C {@code va_list} at its current position. Copying is useful to traverse the va list's elements * Copies this C {@code va_list} at its current position. Copying is useful to traverse the va list's elements
* starting from the current position, without affecting the state of the original va list, essentially * starting from the current position, without affecting the state of the original va list, essentially
* allowing the elements to be traversed multiple times. * allowing the elements to be traversed multiple times.
* <p> * <p>
* If this method needs to allocate native memory for the copy, it will use * Any native resource required by the execution of this method will be allocated in the resource scope
* {@link MemorySegment#allocateNative(long, long)} to do so. {@link #close()} will have to be called on the * associated with this instance (see {@link #scope()}).
* returned va list instance to release the allocated memory.
* <p> * <p>
* This method only copies the va list cursor itself and not the memory that may be attached to the * This method only copies the va list cursor itself and not the memory that may be attached to the
* va list which holds its elements. That means that if this va list was created with the * va list which holds its elements. That means that if this va list was created with the
* {@link #make(Consumer)} method, closing this va list will also release the native memory that holds its * {@link #make(Consumer, ResourceScope)} method, closing this va list will also release the native memory that holds its
* elements, making the copy unusable. * elements, making the copy unusable.
* *
* @return a copy of this C {@code va_list}. * @return a copy of this C {@code va_list}.
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid * @throws IllegalStateException if the resource scope associated with this instance has been closed
* (see {@link #close()}). * (see {@link #scope()}).
*/ */
VaList copy(); VaList copy();
/**
* Copies this C {@code va_list} at its current position. Copying is useful to traverse the va list's elements
* starting from the current position, without affecting the state of the original va list, essentially
* allowing the elements to be traversed multiple times.
* <p>
* If this method needs to allocate native memory for the copy, it will use
* the given {@code NativeScope} to do so.
* <p>
* This method only copies the va list cursor itself and not the memory that may be attached to the
* va list which holds its elements. That means that if this va list was created with the
* {@link #make(Consumer)} method, closing this va list will also release the native memory that holds its
* elements, making the copy unusable.
*
* @param scope the scope to allocate the copy in
* @return a copy of this C {@code va_list}.
* @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
* (see {@link #close()}).
*/
VaList copy(NativeScope scope);
/** /**
* Returns the memory address of the C {@code va_list} associated with this instance. * Returns the memory address of the C {@code va_list} associated with this instance.
* The returned memory address is associated with same resource scope as that associated with this instance.
* *
* @return the memory address of the C {@code va_list} associated with this instance. * @return the memory address of the C {@code va_list} associated with this instance.
*/ */
@ -589,51 +618,58 @@ public interface CLinker {
MemoryAddress address(); MemoryAddress address();
/** /**
* Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list}. * Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list},
* backed by the {@linkplain ResourceScope#globalScope() global} resource scope.
* <p> * <p>
* This method is <em>restricted</em>. Restricted method are unsafe, and, if used incorrectly, their use might crash * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
* *
* @param address a memory address pointing to an existing C {@code va_list}. * @param address a memory address pointing to an existing C {@code va_list}.
* @return a new {@code VaList} instance backed by the C {@code va_list} at {@code address}. * @return a new {@code VaList} instance backed by the C {@code va_list} at {@code address}.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static VaList ofAddressRestricted(MemoryAddress address) { @CallerSensitive
Utils.checkRestrictedAccess("VaList.ofAddressRestricted"); static VaList ofAddress(MemoryAddress address) {
Objects.requireNonNull(address); Reflection.ensureNativeAccess(Reflection.getCallerClass());
return SharedUtils.newVaListOfAddress(address); return SharedUtils.newVaListOfAddress(address, ResourceScope.globalScope());
} }
/** /**
* Constructs a new {@code VaList} using a builder (see {@link Builder}). * Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list},
* with given resource scope.
* <p> * <p>
* If this method needs to allocate native memory for the va list, it will use * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* {@link MemorySegment#allocateNative(long, long)} to do so. * Restricted method are unsafe, and, if used incorrectly, their use might crash
* <p> * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* This method will allocate native memory to hold the elements in the va list. This memory * restricted methods, and use safe and supported functionalities, where possible.
* will be 'attached' to the returned va list instance, and will be released when {@link VaList#close()}
* is called.
* <p>
* Note that when there are no elements added to the created va list,
* this method will return the same as {@link #empty()}.
* *
* @param actions a consumer for a builder (see {@link Builder}) which can be used to specify the elements * @param address a memory address pointing to an existing C {@code va_list}.
* of the underlying C {@code va_list}. * @param scope the resource scope to be associated with the returned {@code VaList} instance.
* @return a new {@code VaList} instance backed by a fresh C {@code va_list}. * @return a new {@code VaList} instance backed by the C {@code va_list} at {@code address}.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
static VaList make(Consumer<Builder> actions) { @CallerSensitive
Objects.requireNonNull(actions); static VaList ofAddress(MemoryAddress address, ResourceScope scope) {
return SharedUtils.newVaList(actions, MemorySegment::allocateNative); Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(address);
Objects.requireNonNull(scope);
return SharedUtils.newVaListOfAddress(address, scope);
} }
/** /**
* Constructs a new {@code VaList} using a builder (see {@link Builder}). * Constructs a new {@code VaList} using a builder (see {@link Builder}), associated with a given
* {@linkplain ResourceScope resource scope}.
* <p> * <p>
* If this method needs to allocate native memory for the va list, it will use * If this method needs to allocate native memory, such memory will be managed by the given
* the given {@code NativeScope} to do so. * {@linkplain ResourceScope resource scope}, and will be released when the resource scope is {@linkplain ResourceScope#close closed}.
* <p>
* This method will allocate native memory to hold the elements in the va list. This memory
* will be managed by the given {@code NativeScope}, and will be released when the scope is closed.
* <p> * <p>
* Note that when there are no elements added to the created va list, * Note that when there are no elements added to the created va list,
* this method will return the same as {@link #empty()}. * this method will return the same as {@link #empty()}.
@ -642,11 +678,13 @@ public interface CLinker {
* of the underlying C {@code va_list}. * of the underlying C {@code va_list}.
* @param scope the scope to be used for the valist allocation. * @param scope the scope to be used for the valist allocation.
* @return a new {@code VaList} instance backed by a fresh C {@code va_list}. * @return a new {@code VaList} instance backed by a fresh C {@code va_list}.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/ */
static VaList make(Consumer<Builder> actions, NativeScope scope) { static VaList make(Consumer<Builder> actions, ResourceScope scope) {
Objects.requireNonNull(actions); Objects.requireNonNull(actions);
Objects.requireNonNull(scope); Objects.requireNonNull(scope);
return SharedUtils.newVaList(actions, SharedUtils.Allocator.ofScope(scope)); return SharedUtils.newVaList(actions, scope);
} }
/** /**
@ -665,13 +703,8 @@ public interface CLinker {
* *
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null} * <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p> * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
*
* @apiNote In the future, if the Java language permits, {@link Builder}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
*/ */
interface Builder { sealed interface Builder permits WinVaList.Builder, SysVVaList.Builder, AArch64VaList.Builder {
/** /**
* Adds a native value represented as an {@code int} to the C {@code va_list} being constructed. * Adds a native value represented as an {@code int} to the C {@code va_list} being constructed.
@ -729,7 +762,7 @@ public interface CLinker {
* A C type kind. Each kind corresponds to a particular C language builtin type, and can be attached to * A C type kind. Each kind corresponds to a particular C language builtin type, and can be attached to
* {@link ValueLayout} instances using the {@link MemoryLayout#withAttribute(String, Constable)} in order * {@link ValueLayout} instances using the {@link MemoryLayout#withAttribute(String, Constable)} in order
* to obtain a layout which can be classified accordingly by {@link CLinker#downcallHandle(Addressable, MethodType, FunctionDescriptor)} * to obtain a layout which can be classified accordingly by {@link CLinker#downcallHandle(Addressable, MethodType, FunctionDescriptor)}
* and {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor)}. * and {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor, ResourceScope)}.
*/ */
enum TypeKind { enum TypeKind {
/** /**
@ -806,6 +839,6 @@ public interface CLinker {
TypeKind = layout.attribute(TypeKind.ATTR_NAME).orElse(null); TypeKind = layout.attribute(TypeKind.ATTR_NAME).orElse(null);
* }</pre></blockquote> * }</pre></blockquote>
*/ */
public final static String ATTR_NAME = "abi/kind"; public static final String ATTR_NAME = "abi/kind";
} }
} }

View file

@ -42,8 +42,8 @@ import java.util.stream.Collectors;
/** /**
* A group layout is used to combine together multiple <em>member layouts</em>. There are two ways in which member layouts * A group layout is used to combine together multiple <em>member layouts</em>. There are two ways in which member layouts
* can be combined: if member layouts are laid out one after the other, the resulting group layout is said to be a <em>struct</em> * can be combined: if member layouts are laid out one after the other, the resulting group layout is said to be a <em>struct</em>
* (see {@link MemoryLayout#ofStruct(MemoryLayout...)}); conversely, if all member layouts are laid out at the same starting offset, * (see {@link MemoryLayout#structLayout(MemoryLayout...)}); conversely, if all member layouts are laid out at the same starting offset,
* the resulting group layout is said to be a <em>union</em> (see {@link MemoryLayout#ofUnion(MemoryLayout...)}). * the resulting group layout is said to be a <em>union</em> (see {@link MemoryLayout#unionLayout(MemoryLayout...)}).
* <p> * <p>
* This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a> * This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>
* class; programmers should treat instances that are * class; programmers should treat instances that are
@ -58,7 +58,7 @@ import java.util.stream.Collectors;
* @implSpec * @implSpec
* This class is immutable and thread-safe. * This class is immutable and thread-safe.
*/ */
public final class GroupLayout extends AbstractLayout { public final class GroupLayout extends AbstractLayout implements MemoryLayout {
/** /**
* The group kind. * The group kind.
@ -118,8 +118,8 @@ public final class GroupLayout extends AbstractLayout {
* Returns the member layouts associated with this group. * Returns the member layouts associated with this group.
* *
* @apiNote the order in which member layouts are returned is the same order in which member layouts have * @apiNote the order in which member layouts are returned is the same order in which member layouts have
* been passed to one of the group layout factory methods (see {@link MemoryLayout#ofStruct(MemoryLayout...)}, * been passed to one of the group layout factory methods (see {@link MemoryLayout#structLayout(MemoryLayout...)},
* {@link MemoryLayout#ofUnion(MemoryLayout...)}). * {@link MemoryLayout#unionLayout(MemoryLayout...)}).
* *
* @return the member layouts associated with this group. * @return the member layouts associated with this group.
*/ */

View file

@ -26,6 +26,8 @@
package jdk.incubator.foreign; package jdk.incubator.foreign;
import jdk.internal.foreign.LibrariesHelper; import jdk.internal.foreign.LibrariesHelper;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import java.io.File; import java.io.File;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
@ -37,23 +39,22 @@ import java.util.Optional;
* A native library lookup. Exposes a lookup operation for searching symbols, see {@link LibraryLookup#lookup(String)}. * A native library lookup. Exposes a lookup operation for searching symbols, see {@link LibraryLookup#lookup(String)}.
* A given native library remains loaded as long as there is at least one <em>live</em> library lookup instance referring * A given native library remains loaded as long as there is at least one <em>live</em> library lookup instance referring
* to it. * to it.
* All symbol instances (see {@link LibraryLookup.Symbol}) generated by a given library lookup object contain a strong reference * All instances generated by a given library lookup object contain a strong reference to said lookup object,
* to said lookup object, therefore preventing library unloading; in turn method handle instances obtained from * therefore preventing library unloading. For {@linkplain #lookup(String, MemoryLayout) memory segments} obtained from a library lookup object,
* {@link CLinker#downcallHandle(Addressable, MethodType, FunctionDescriptor)}) also maintain a strong reference * this means that clients can safely dereference memory associated with lookup symbols, as follows:
* to the addressable parameter used for their construction. This means that there is always a strong reachability chain
* from a native method handle to a lookup object (the one that was used to lookup the native library symbol the method handle
* refers to); this is useful to prevent situations where a native library is unloaded in the middle of a native call.
* <p><a id = "var-symbols"></a></p>
* In cases where a client wants to create a memory segment out of a lookup symbol, the client might want to attach the
* lookup symbol to the newly created segment, so that the symbol will be kept reachable as long as the memory segment
* is reachable; this can be achieved by creating the segment using the {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)}
* restricted segment factory, as follows:
* <pre>{@code * <pre>{@code
LibraryLookup defaultLookup = LibraryLookup.defaultLookup(); * LibraryLookup defaultLookup = LibraryLookup.ofDefault();
LibraryLookup.Symbol errno = defaultLookup.lookup("errno"); * MemorySegment errnoSegment = defaultLookup.lookup("errno", MemoryLayouts.JAVA_INT).get();
MemorySegment errnoSegment = errno.address().asRestrictedSegment(4, errno); * int errno = MemoryAccess.getInt(errnoSegment);
* }</pre> * }</pre>
* <p> * <p>
* For {@linkplain #lookup(String) memory addresses} obtained from a library lookup object,
* since {@linkplain CLinker#downcallHandle(Addressable, MethodType, FunctionDescriptor) native method handles}
* also maintain a strong reference to the addressable parameter used for their construction, there is
* always a strong reachability chain from a native method handle to a lookup object (the one that was used to lookup
* the native library symbol the method handle refers to). This is useful to prevent situations where a native library
* is unloaded in the middle of a native call.
* <p>
* To allow for a library to be unloaded, a client will have to discard any strong references it * To allow for a library to be unloaded, a client will have to discard any strong references it
* maintains, directly, or indirectly to a lookup object associated with given library. * maintains, directly, or indirectly to a lookup object associated with given library.
* *
@ -63,45 +64,42 @@ MemorySegment errnoSegment = errno.address().asRestrictedSegment(4, errno);
public interface LibraryLookup { public interface LibraryLookup {
/** /**
* A symbol retrieved during a library lookup. A lookup symbol has a <em>name</em> and can be projected * Looks up a symbol with given name in this library. The returned memory address maintains a strong reference to this lookup object.
* into a memory address (see {@link #name()} and {@link #address()}, respectively).
* *
* @apiNote In the future, if the Java language permits, {@link Symbol}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
* @implSpec
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
*/
interface Symbol extends Addressable {
/**
* The name of this lookup symbol.
* @return the name of this lookup symbol.
*/
String name();
/**
* The memory address of this lookup symbol. If the memory associated with this symbol needs to be dereferenced,
* clients can obtain a segment from this symbol's address using the {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)},
* and making sure that the created segment maintains a <a href="LibraryLookup.html#var-symbols">strong reference</a> to this symbol, to prevent library unloading.
* @return the memory address of this lookup symbol.
*/
@Override
MemoryAddress address();
}
/**
* Looks up a symbol with given name in this library. The returned symbol maintains a strong reference to this lookup object.
* @param name the symbol name. * @param name the symbol name.
* @return the library symbol (if any). * @return the memory address associated with the library symbol (if any).
*/ */
Optional<Symbol> lookup(String name); Optional<MemoryAddress> lookup(String name);
/**
* Looks up a symbol with given name in this library. The returned memory segment has a size that matches that of
* the specified layout, and maintains a strong reference to this lookup object. This method can be useful
* to lookup global variable symbols in a foreign library.
*
* @param name the symbol name.
* @param layout the layout to be associated with the library symbol.
* @return the memory segment associated with the library symbol (if any).
* @throws IllegalArgumentException if the address associated with the lookup symbol do not match the
* {@linkplain MemoryLayout#byteAlignment() alignment constraints} in {@code layout}.
*/
Optional<MemorySegment> lookup(String name, MemoryLayout layout);
/** /**
* Obtain a default library lookup object. * Obtain a default library lookup object.
* <p>
* This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible.
*
* @return the default library lookup object. * @return the default library lookup object.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
@CallerSensitive
static LibraryLookup ofDefault() { static LibraryLookup ofDefault() {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
SecurityManager security = System.getSecurityManager(); SecurityManager security = System.getSecurityManager();
if (security != null) { if (security != null) {
security.checkPermission(new RuntimePermission("java.foreign.getDefaultLibrary")); security.checkPermission(new RuntimePermission("java.foreign.getDefaultLibrary"));
@ -111,12 +109,23 @@ public interface LibraryLookup {
/** /**
* Obtain a library lookup object corresponding to a library identified by given path. * Obtain a library lookup object corresponding to a library identified by given path.
* <p>
* This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible.
*
* @param path the library absolute path. * @param path the library absolute path.
* @return a library lookup object for given path. * @return a library lookup object for given path.
* @throws IllegalArgumentException if the specified path does not correspond to an absolute path, * @throws IllegalArgumentException if the specified path does not correspond to an absolute path,
* e.g. if {@code !path.isAbsolute()}. * e.g. if {@code !path.isAbsolute()}.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
@CallerSensitive
static LibraryLookup ofPath(Path path) { static LibraryLookup ofPath(Path path) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(path); Objects.requireNonNull(path);
if (!path.isAbsolute()) { if (!path.isAbsolute()) {
throw new IllegalArgumentException("Not an absolute path: " + path.toString()); throw new IllegalArgumentException("Not an absolute path: " + path.toString());
@ -134,10 +143,21 @@ public interface LibraryLookup {
* is decorated according to the platform conventions (e.g. on Linux, the {@code lib} prefix is added, * is decorated according to the platform conventions (e.g. on Linux, the {@code lib} prefix is added,
* as well as the {@code .so} extension); the resulting name is then looked up in the standard native * as well as the {@code .so} extension); the resulting name is then looked up in the standard native
* library path (which can be overriden, by setting the <code>java.library.path</code> property). * library path (which can be overriden, by setting the <code>java.library.path</code> property).
* <p>
* This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible.
*
* @param libName the library name. * @param libName the library name.
* @return a library lookup object for given library name. * @return a library lookup object for given library name.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
@CallerSensitive
static LibraryLookup ofLibrary(String libName) { static LibraryLookup ofLibrary(String libName) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(libName); Objects.requireNonNull(libName);
SecurityManager security = System.getSecurityManager(); SecurityManager security = System.getSecurityManager();
if (security != null) { if (security != null) {

View file

@ -1,167 +0,0 @@
/*
* Copyright (c) 2020, 2021, 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.incubator.foreign;
import jdk.internal.foreign.MappedMemorySegmentImpl;
import java.io.UncheckedIOException;
import java.nio.MappedByteBuffer;
import java.util.Objects;
/**
* This class provides capabilities to manipulate mapped memory segments, such as {@link #force(MemorySegment)},
* and {@link #load(MemorySegment)}. The methods in these class are suitable replacements for some of the
* functionality in the {@link java.nio.MappedByteBuffer} class. Note that, while it is possible to map a segment
* into a byte buffer (see {@link MemorySegment#asByteBuffer()}), and call e.g. {@link MappedByteBuffer#force()} that way,
* this can only be done when the source segment is small enough, due to the size limitation inherent to the
* ByteBuffer API.
* <p>
* Clients requiring sophisticated, low-level control over mapped memory segments, should consider writing
* custom mapped memory segment factories; using JNI, e.g. on Linux, it is possible to call {@code mmap}
* with the desired parameters; the returned address can be easily wrapped into a memory segment, using
* {@link MemoryAddress#ofLong(long)} and {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)}.
*
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
*
* @implNote
* The behavior of some the methods in this class (see {@link #load(MemorySegment)}, {@link #unload(MemorySegment)} and
* {@link #isLoaded(MemorySegment)}) is highly platform-dependent; as a result, calling these methods might
* be a no-op on certain platforms.
*/
public final class MappedMemorySegments {
private MappedMemorySegments() {
// no thanks
}
/**
* Tells whether or not the contents of the given segment is resident in physical
* memory.
*
* <p> A return value of {@code true} implies that it is highly likely
* that all of the data in the given segment is resident in physical memory and
* may therefore be accessed without incurring any virtual-memory page
* faults or I/O operations. A return value of {@code false} does not
* necessarily imply that the segment's content is not resident in physical
* memory.
*
* <p> The returned value is a hint, rather than a guarantee, because the
* underlying operating system may have paged out some of the segment's data
* by the time that an invocation of this method returns. </p>
*
* @param segment the segment whose contents are to be tested.
* @return {@code true} if it is likely that the contents of the given segment
* is resident in physical memory
*
* @throws IllegalStateException if the given segment is not alive, or if the given segment is confined
* and this method is called from a thread other than the segment's owner thread.
* @throws UnsupportedOperationException if the given segment is not a mapped memory segment, e.g. if
* {@code segment.isMapped() == false}.
*/
public static boolean isLoaded(MemorySegment segment) {
return toMappedSegment(segment).isLoaded();
}
/**
* Loads the contents of the given segment into physical memory.
*
* <p> This method makes a best effort to ensure that, when it returns,
* this contents of the given segment is resident in physical memory. Invoking this
* method may cause some number of page faults and I/O operations to
* occur. </p>
*
* @param segment the segment whose contents are to be loaded.
*
* @throws IllegalStateException if the given segment is not alive, or if the given segment is confined
* and this method is called from a thread other than the segment's owner thread.
* @throws UnsupportedOperationException if the given segment is not a mapped memory segment, e.g. if
* {@code segment.isMapped() == false}.
*/
public static void load(MemorySegment segment) {
toMappedSegment(segment).load();
}
/**
* Unloads the contents of the given segment from physical memory.
*
* <p> This method makes a best effort to ensure that the contents of the given segment are
* are no longer resident in physical memory. Accessing this segment's contents
* after invoking this method may cause some number of page faults and I/O operations to
* occur (as this segment's contents might need to be paged back in). </p>
*
* @param segment the segment whose contents are to be unloaded.
*
* @throws IllegalStateException if the given segment is not alive, or if the given segment is confined
* and this method is called from a thread other than the segment's owner thread.
* @throws UnsupportedOperationException if the given segment is not a mapped memory segment, e.g. if
* {@code segment.isMapped() == false}.
*/
public static void unload(MemorySegment segment) {
toMappedSegment(segment).unload();
}
/**
* Forces any changes made to the contents of the given segment to be written to the
* storage device described by the mapped segment's file descriptor.
*
* <p> If this mapping's file descriptor resides on a local storage
* device then when this method returns it is guaranteed that all changes
* made to the segment since it was created, or since this method was last
* invoked, will have been written to that device.
*
* <p> If this mapping's file descriptor does not reside on a local device then no such guarantee
* is made.
*
* <p> If the given segment was not mapped in read/write mode ({@link
* java.nio.channels.FileChannel.MapMode#READ_WRITE}) then
* invoking this method may have no effect. In particular, the
* method has no effect for segments mapped in read-only or private
* mapping modes. This method may or may not have an effect for
* implementation-specific mapping modes.
* </p>
*
* @param segment the segment whose contents are to be written to the storage device described by the
* segment's file descriptor.
*
* @throws IllegalStateException if the given segment is not alive, or if the given segment is confined
* and this method is called from a thread other than the segment's owner thread.
* @throws UnsupportedOperationException if the given segment is not a mapped memory segment, e.g. if
* {@code segment.isMapped() == false}.
* @throws UncheckedIOException if there is an I/O error writing the contents of the segment to the associated storage device
*/
public static void force(MemorySegment segment) {
toMappedSegment(segment).force();
}
static MappedMemorySegmentImpl toMappedSegment(MemorySegment segment) {
Objects.requireNonNull(segment);
if (segment instanceof MappedMemorySegmentImpl) {
return (MappedMemorySegmentImpl)segment;
} else {
throw new UnsupportedOperationException("Not a mapped memory segment");
}
}
}

View file

@ -26,19 +26,28 @@
package jdk.incubator.foreign; package jdk.incubator.foreign;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.MemoryAddressImpl; import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.NativeMemorySegmentImpl; import jdk.internal.ref.CleanerFactory;
import jdk.internal.foreign.Utils; import jdk.internal.reflect.CallerSensitive;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
/** /**
* A memory address models a reference into a memory location. Memory addresses are typically obtained using the * A memory address models a reference into a memory location. Memory addresses are typically obtained using the
* {@link MemorySegment#address()} method, and can refer to either off-heap or on-heap memory. * {@link MemorySegment#address()} method, and can refer to either off-heap or on-heap memory. Off-heap memory
* addresses are referred to as <em>native</em> memory addresses (see {@link #isNative()}). Native memory addresses
* allow clients to obtain a raw memory address (expressed as a long value) which can then be used e.g. when interacting
* with native code.
* <p>
* Given an address, it is possible to compute its offset relative to a given segment, which can be useful * Given an address, it is possible to compute its offset relative to a given segment, which can be useful
* when performing memory dereference operations using a memory access var handle (see {@link MemoryHandles}). * when performing memory dereference operations using a memory access var handle (see {@link MemoryHandles}).
* <p> * <p>
* A memory address is associated with a {@linkplain ResourceScope resource scope}; the resource scope determines the
* lifecycle of the memory address, and whether the address can be used from multiple threads. Memory addresses
* obtained from {@linkplain #ofLong(long) numeric values}, or from native code, are associated with the
* {@linkplain ResourceScope#globalScope() global resource scope}. Memory addresses obtained from segments
* are associated with the same scope as the segment from which they have been obtained.
* <p>
* All implementations of this interface must be <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>; * All implementations of this interface must be <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>;
* programmers should treat instances that are {@linkplain #equals(Object) equal} as interchangeable and should not * programmers should treat instances that are {@linkplain #equals(Object) equal} as interchangeable and should not
* use instances for synchronization, or unpredictable behavior may occur. For example, in a future release, * use instances for synchronization, or unpredictable behavior may occur. For example, in a future release,
@ -49,14 +58,10 @@ import java.lang.ref.Cleaner;
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null} * <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p> * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
* *
* @apiNote In the future, if the Java language permits, {@link MemoryAddress}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
* @implSpec * @implSpec
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>. * Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
*/ */
public interface MemoryAddress extends Addressable { public sealed interface MemoryAddress extends Addressable permits MemoryAddressImpl {
@Override @Override
default MemoryAddress address() { default MemoryAddress address() {
@ -70,9 +75,15 @@ public interface MemoryAddress extends Addressable {
*/ */
MemoryAddress addOffset(long offset); MemoryAddress addOffset(long offset);
/**
* Returns the resource scope associated with this memory address.
* @return the resource scope associated with this memory address.
*/
ResourceScope scope();
/** /**
* Returns the offset of this memory address into the given segment. More specifically, if both the segment's * Returns the offset of this memory address into the given segment. More specifically, if both the segment's
* base address and this address are off-heap addresses, the result is computed as * base address and this address are native addresses, the result is computed as
* {@code this.toRawLongValue() - segment.address().toRawLongValue()}. Otherwise, if both addresses in the form * {@code this.toRawLongValue() - segment.address().toRawLongValue()}. Otherwise, if both addresses in the form
* {@code (B, O1)}, {@code (B, O2)}, where {@code B} is the same base heap object and {@code O1}, {@code O2} * {@code (B, O1)}, {@code (B, O2)}, where {@code B} is the same base heap object and {@code O1}, {@code O2}
* are byte offsets (relative to the base object) associated with this address and the segment's base address, * are byte offsets (relative to the base object) associated with this address and the segment's base address,
@ -86,82 +97,94 @@ public interface MemoryAddress extends Addressable {
* @return the offset of this memory address into the given segment. * @return the offset of this memory address into the given segment.
* @param segment the segment relative to which this address offset should be computed * @param segment the segment relative to which this address offset should be computed
* @throws IllegalArgumentException if {@code segment} is not compatible with this address; this can happen, for instance, * @throws IllegalArgumentException if {@code segment} is not compatible with this address; this can happen, for instance,
* when {@code segment} models an heap memory region, while this address models an off-heap memory address. * when {@code segment} models an heap memory region, while this address is a {@linkplain #isNative() native} address.
*/ */
long segmentOffset(MemorySegment segment); long segmentOffset(MemorySegment segment);
/** /**
* Returns a new confined native memory segment with given size, and whose base address is this address; the returned segment has its own temporal Returns a new native memory segment with given size and resource scope (replacing the scope already associated
* bounds, and can therefore be closed. This method can be useful when interacting with custom native memory sources (e.g. custom allocators), * with this address), and whose base address is this address. This method can be useful when interacting with custom
* where an address to some underlying memory region is typically obtained from native code (often as a plain {@code long} value). * native memory sources (e.g. custom allocators), where an address to some
* <p> * underlying memory region is typically obtained from native code (often as a plain {@code long} value).
* The returned segment will feature all <a href="#access-modes">access modes</a> * The returned segment is not read-only (see {@link MemorySegment#isReadOnly()}), and is associated with the
* (see {@link MemorySegment#ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}). * provided resource scope.
* <p> * <p>
* Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and, * Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and,
* if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value, * if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value,
* have no visible effect, or cause an unspecified exception to be thrown. * have no visible effect, or cause an unspecified exception to be thrown.
* <p> * <p>
* Calling {@link MemorySegment#close()} on the returned segment will <em>not</em> result in releasing any * This method is equivalent to the following code:
* memory resources which might implicitly be associated with the segment. This method is equivalent to the following code:
* <pre>{@code * <pre>{@code
asSegmentRestricted(byteSize, null, null); asSegment(byteSize, null, scope);
* }</pre> * }</pre>
* This method is <em>restricted</em>. Restricted methods are unsafe, and, if used incorrectly, their use might crash * <p>
* This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
* *
* @param bytesSize the desired size. * @param bytesSize the desired size.
* @return a new confined native memory segment with given base address and size. * @param scope the native segment scope.
* @return a new native memory segment with given base address, size and scope.
* @throws IllegalArgumentException if {@code bytesSize <= 0}. * @throws IllegalArgumentException if {@code bytesSize <= 0}.
* @throws UnsupportedOperationException if this address is an heap address. * @throws IllegalStateException if either the scope associated with this address or the provided scope
* @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either * have been already closed, or if access occurs from a thread other than the thread owning either
* {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}). * scopes.
* @throws UnsupportedOperationException if this address is not a {@linkplain #isNative() native} address.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
default MemorySegment asSegmentRestricted(long bytesSize) { @CallerSensitive
return asSegmentRestricted(bytesSize, null, null); MemorySegment asSegment(long bytesSize, ResourceScope scope);
}
/** /**
* Returns a new confined native memory segment with given size, and whose base address is this address; the returned segment has its own temporal * Returns a new native memory segment with given size and resource scope (replacing the scope already associated
* bounds, and can therefore be closed. This method can be useful when interacting with custom native memory sources (e.g. custom allocators), * with this address), and whose base address is this address. This method can be useful when interacting with custom
* where an address to some underlying memory region is typically obtained from native code (often as a plain {@code long} value). * native memory sources (e.g. custom allocators), where an address to some
* <p> * underlying memory region is typically obtained from native code (often as a plain {@code long} value).
* The returned segment will feature all <a href="#access-modes">access modes</a> * The returned segment is associated with the provided resource scope.
* (see {@link MemorySegment#ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
* Moreover, the returned segment will keep a strong reference to the supplied attachment object (if any), which can
* be useful in cases where the lifecycle of the segment is dependent on that of some other external resource.
* <p> * <p>
* Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and, * Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and,
* if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value, * if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value,
* have no visible effect, or cause an unspecified exception to be thrown. * have no visible effect, or cause an unspecified exception to be thrown.
* <p> * <p>
* Calling {@link MemorySegment#close()} on the returned segment will <em>not</em> result in releasing any * Calling {@link ResourceScope#close()} on the scope associated with the returned segment will result in calling
* memory resources which might implicitly be associated with the segment, but will result in calling the * the provided cleanup action (if any).
* provided cleanup action (if any).
* <p> * <p>
* Both the cleanup action and the attachment object (if any) will be preserved under terminal operations such as * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
* {@link MemorySegment#handoff(Thread)}, {@link MemorySegment#share()} and {@link MemorySegment#registerCleaner(Cleaner)}. * Restricted method are unsafe, and, if used incorrectly, their use might crash
* <p>
* This method is <em>restricted</em>. Restricted methods are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible. * restricted methods, and use safe and supported functionalities, where possible.
* *
* @param bytesSize the desired size. * @param bytesSize the desired size.
* @param cleanupAction the cleanup action; can be {@code null}. * @param cleanupAction the cleanup action; can be {@code null}.
* @param attachment an attachment object that will be kept strongly reachable by the returned segment; can be {@code null}. * @param scope the native segment scope.
* @return a new confined native memory segment with given base address and size. * @return a new native memory segment with given base address, size and scope.
* @throws IllegalArgumentException if {@code bytesSize <= 0}. * @throws IllegalArgumentException if {@code bytesSize <= 0}.
* @throws UnsupportedOperationException if this address is an heap address. * @throws IllegalStateException if either the scope associated with this address or the provided scope
* @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either * have been already closed, or if access occurs from a thread other than the thread owning either
* {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}). * scopes.
* @throws UnsupportedOperationException if this address is not a {@linkplain #isNative() native} address.
* @throws IllegalCallerException if access to this method occurs from a module {@code M} and the command line option
* {@code --enable-native-access} is either absent, or does not mention the module name {@code M}, or
* {@code ALL-UNNAMED} in case {@code M} is an unnamed module.
*/ */
MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, Object attachment); @CallerSensitive
MemorySegment asSegment(long bytesSize, Runnable cleanupAction, ResourceScope scope);
/** /**
* Returns the raw long value associated with this memory address. * Is this an off-heap memory address?
* @return The raw long value associated with this memory address. * @return true, if this is an off-heap memory address.
* @throws UnsupportedOperationException if this memory address is an heap address. */
boolean isNative();
/**
* Returns the raw long value associated with this native memory address.
* @return The raw long value associated with this native memory address.
* @throws UnsupportedOperationException if this memory address is not a {@linkplain #isNative() native} address.
* @throws IllegalStateException if the scope associated with this segment has been already closed,
* or if access occurs from a thread other than the thread owning either segment.
*/ */
long toRawLongValue(); long toRawLongValue();
@ -169,11 +192,10 @@ public interface MemoryAddress extends Addressable {
* Compares the specified object with this address for equality. Returns {@code true} if and only if the specified * Compares the specified object with this address for equality. Returns {@code true} if and only if the specified
* object is also an address, and it refers to the same memory location as this address. * object is also an address, and it refers to the same memory location as this address.
* *
* @apiNote two addresses might be considered equal despite their associated segments differ. This * @apiNote two addresses might be considered equal despite their associated resource scopes differ. This
* can happen, for instance, if the segment associated with one address is a <em>slice</em> * can happen, for instance, if the same memory address is used to create memory segments with different
* (see {@link MemorySegment#asSlice(long, long)}) of the segment associated with the other address. Moreover, * scopes (using {@link #asSegment(long, ResourceScope)}), and the base address of the resulting segments is
* two addresses might be considered equals despite differences in the temporal bounds associated with their * then compared.
* corresponding segments.
* *
* @param that the object to be compared for equality with this address. * @param that the object to be compared for equality with this address.
* @return {@code true} if the specified object is equal to this address. * @return {@code true} if the specified object is equal to this address.
@ -189,12 +211,14 @@ public interface MemoryAddress extends Addressable {
int hashCode(); int hashCode();
/** /**
* The off-heap memory address instance modelling the {@code NULL} address. * The native memory address instance modelling the {@code NULL} address, associated
* with the {@linkplain ResourceScope#globalScope() global} resource scope.
*/ */
MemoryAddress NULL = new MemoryAddressImpl(null, 0L); MemoryAddress NULL = new MemoryAddressImpl(null, 0L);
/** /**
* Obtain an off-heap memory address instance from given long address. * Obtain a native memory address instance from given long address. The returned address is associated
* with the {@linkplain ResourceScope#globalScope() global} resource scope.
* @param value the long address. * @param value the long address.
* @return the new memory address instance. * @return the new memory address instance.
*/ */

View file

@ -44,16 +44,16 @@ import java.util.Objects;
* (see {@link MemoryHandles#varHandle(Class, ByteOrder)}, * (see {@link MemoryHandles#varHandle(Class, ByteOrder)},
* {@link MemoryHandles#varHandle(Class, long, ByteOrder)}). This determines the variable type * {@link MemoryHandles#varHandle(Class, long, ByteOrder)}). This determines the variable type
* (all primitive types but {@code void} and {@code boolean} are supported), as well as the alignment constraint and the * (all primitive types but {@code void} and {@code boolean} are supported), as well as the alignment constraint and the
* byte order associated to a memory access var handle. The resulting memory access var handle can then be combined in various ways * byte order associated with a memory access var handle. The resulting memory access var handle can then be combined in various ways
* to emulate different addressing modes. The var handles created by this class feature a <em>mandatory</em> coordinate type * to emulate different addressing modes. The var handles created by this class feature a <em>mandatory</em> coordinate type
* (of type {@link MemorySegment}), and one {@code long} coordinate type, which represents the offset, in bytes, relative * (of type {@link MemorySegment}), and one {@code long} coordinate type, which represents the offset, in bytes, relative
* to the segment, at which dereference should occur. * to the segment, at which dereference should occur.
* <p> * <p>
* As an example, consider the memory layout expressed by a {@link GroupLayout} instance constructed as follows: * As an example, consider the memory layout expressed by a {@link GroupLayout} instance constructed as follows:
* <blockquote><pre>{@code * <blockquote><pre>{@code
GroupLayout seq = MemoryLayout.ofStruct( GroupLayout seq = MemoryLayout.structLayout(
MemoryLayout.ofPaddingBits(32), MemoryLayout.paddingLayout(32),
MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN).withName("value") MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN).withName("value")
); );
* }</pre></blockquote> * }</pre></blockquote>
* To access the member layout named {@code value}, we can construct a memory access var handle as follows: * To access the member layout named {@code value}, we can construct a memory access var handle as follows:
@ -103,7 +103,7 @@ handle = MemoryHandles.insertCoordinates(handle, 1, 4); //(MemorySegment) -> int
*/ */
public final class MemoryHandles { public final class MemoryHandles {
private final static JavaLangInvokeAccess JLI = SharedSecrets.getJavaLangInvokeAccess(); private static final JavaLangInvokeAccess JLI = SharedSecrets.getJavaLangInvokeAccess();
private MemoryHandles() { private MemoryHandles() {
//sorry, just the one! //sorry, just the one!
@ -254,7 +254,7 @@ public final class MemoryHandles {
* Java {@code int} to avoid dealing with negative values, which would be * Java {@code int} to avoid dealing with negative values, which would be
* the case if modeled as a Java {@code short}. This is illustrated in the following example: * the case if modeled as a Java {@code short}. This is illustrated in the following example:
* <blockquote><pre>{@code * <blockquote><pre>{@code
MemorySegment segment = MemorySegment.allocateNative(2); MemorySegment segment = MemorySegment.allocateNative(2, ResourceScope.newImplicitScope());
VarHandle SHORT_VH = MemoryLayouts.JAVA_SHORT.varHandle(short.class); VarHandle SHORT_VH = MemoryLayouts.JAVA_SHORT.varHandle(short.class);
VarHandle INT_VH = MemoryHandles.asUnsigned(SHORT_VH, int.class); VarHandle INT_VH = MemoryHandles.asUnsigned(SHORT_VH, int.class);
SHORT_VH.set(segment, (short)-1); SHORT_VH.set(segment, (short)-1);

View file

@ -49,7 +49,7 @@ import java.util.stream.Stream;
* A memory layout can be used to describe the contents of a memory segment in a <em>language neutral</em> fashion. * A memory layout can be used to describe the contents of a memory segment in a <em>language neutral</em> fashion.
* There are two leaves in the layout hierarchy, <em>value layouts</em>, which are used to represent values of given size and kind (see * There are two leaves in the layout hierarchy, <em>value layouts</em>, which are used to represent values of given size and kind (see
* {@link ValueLayout}) and <em>padding layouts</em> which are used, as the name suggests, to represent a portion of a memory * {@link ValueLayout}) and <em>padding layouts</em> which are used, as the name suggests, to represent a portion of a memory
* segment whose contents should be ignored, and which are primarily present for alignment reasons (see {@link MemoryLayout#ofPaddingBits(long)}). * segment whose contents should be ignored, and which are primarily present for alignment reasons (see {@link MemoryLayout#paddingLayout(long)}).
* Some common value layout constants are defined in the {@link MemoryLayouts} class. * Some common value layout constants are defined in the {@link MemoryLayouts} class.
* <p> * <p>
* More complex layouts can be derived from simpler ones: a <em>sequence layout</em> denotes a repetition of one or more * More complex layouts can be derived from simpler ones: a <em>sequence layout</em> denotes a repetition of one or more
@ -68,11 +68,11 @@ import java.util.stream.Stream;
* The above declaration can be modelled using a layout object, as follows: * The above declaration can be modelled using a layout object, as follows:
* *
* <blockquote><pre>{@code * <blockquote><pre>{@code
SequenceLayout taggedValues = MemoryLayout.ofSequence(5, SequenceLayout taggedValues = MemoryLayout.sequenceLayout(5,
MemoryLayout.ofStruct( MemoryLayout.structLayout(
MemoryLayout.ofValueBits(8, ByteOrder.nativeOrder()).withName("kind"), MemoryLayout.valueLayout(8, ByteOrder.nativeOrder()).withName("kind"),
MemoryLayout.ofPaddingBits(24), MemoryLayout.paddingLayout(24),
MemoryLayout.ofValueBits(32, ByteOrder.nativeOrder()).withName("value") MemoryLayout.valueLayout(32, ByteOrder.nativeOrder()).withName("value")
) )
).withName("TaggedValues"); ).withName("TaggedValues");
* }</pre></blockquote> * }</pre></blockquote>
@ -144,17 +144,17 @@ MemoryLayout value = taggedValues.select(PathElement.sequenceElement(),
* *
* And, we can also replace the layout named {@code value} with another layout, as follows: * And, we can also replace the layout named {@code value} with another layout, as follows:
* <blockquote><pre>{@code * <blockquote><pre>{@code
MemoryLayout taggedValuesWithHole = taggedValues.map(l -> MemoryLayout.ofPadding(32), MemoryLayout taggedValuesWithHole = taggedValues.map(l -> MemoryLayout.paddingLayout(32),
PathElement.sequenceElement(), PathElement.groupElement("value")); PathElement.sequenceElement(), PathElement.groupElement("value"));
* }</pre></blockquote> * }</pre></blockquote>
* *
* That is, the above declaration is identical to the following, more verbose one: * That is, the above declaration is identical to the following, more verbose one:
* <blockquote><pre>{@code * <blockquote><pre>{@code
MemoryLayout taggedValuesWithHole = MemoryLayout.ofSequence(5, MemoryLayout taggedValuesWithHole = MemoryLayout.sequenceLayout(5,
MemoryLayout.ofStruct( MemoryLayout.structLayout(
MemoryLayout.ofValueBits(8, ByteOrder.nativeOrder()).withName("kind"). MemoryLayout.valueLayout(8, ByteOrder.nativeOrder()).withName("kind"),
MemoryLayout.ofPaddingBits(32), MemoryLayout.paddingLayout(32),
MemoryLayout.ofPaddingBits(32) MemoryLayout.paddingLayout(32)
)); ));
* }</pre></blockquote> * }</pre></blockquote>
* *
@ -191,17 +191,13 @@ long offset2 = (long) offsetHandle.invokeExact(2L); // 16
* *
* Layouts can be optionally associated with one or more <em>attributes</em>. A layout attribute forms a <em>name/value</em> * Layouts can be optionally associated with one or more <em>attributes</em>. A layout attribute forms a <em>name/value</em>
* pair, where the name is a {@link String} and the value is a {@link Constable}. The most common form of layout attribute * pair, where the name is a {@link String} and the value is a {@link Constable}. The most common form of layout attribute
* is the <em>layout name</em> (see {@link #LAYOUT_NAME}), a custom name that can be associated to memory layouts and that can be referred to when * is the <em>layout name</em> (see {@link #LAYOUT_NAME}), a custom name that can be associated with memory layouts and that can be referred to when
* constructing <a href="MemoryLayout.html#layout-paths"><em>layout paths</em></a>. * constructing <a href="MemoryLayout.html#layout-paths"><em>layout paths</em></a>.
* *
* @apiNote In the future, if the Java language permits, {@link MemoryLayout}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
* @implSpec * @implSpec
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>. * Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
*/ */
public interface MemoryLayout extends Constable { public sealed interface MemoryLayout extends Constable permits AbstractLayout, SequenceLayout, GroupLayout, PaddingLayout, ValueLayout {
/** /**
* Returns an {@link Optional} containing the nominal descriptor for this * Returns an {@link Optional} containing the nominal descriptor for this
@ -218,7 +214,7 @@ public interface MemoryLayout extends Constable {
* Does this layout have a specified size? A layout does not have a specified size if it is (or contains) a sequence layout whose * Does this layout have a specified size? A layout does not have a specified size if it is (or contains) a sequence layout whose
* size is unspecified (see {@link SequenceLayout#elementCount()}). * size is unspecified (see {@link SequenceLayout#elementCount()}).
* *
* Value layouts (see {@link ValueLayout}) and padding layouts (see {@link MemoryLayout#ofPaddingBits(long)}) * Value layouts (see {@link ValueLayout}) and padding layouts (see {@link MemoryLayout#paddingLayout(long)})
* <em>always</em> have a specified size, therefore this method always returns {@code true} in these cases. * <em>always</em> have a specified size, therefore this method always returns {@code true} in these cases.
* *
* @return {@code true}, if this layout has a specified size. * @return {@code true}, if this layout has a specified size.
@ -267,7 +263,7 @@ public interface MemoryLayout extends Constable {
* }</pre></blockquote> * }</pre></blockquote>
* *
* @param name the layout name. * @param name the layout name.
* @return a new layout which is the same as this layout, except for the <em>name</em> associated to it. * @return a new layout which is the same as this layout, except for the <em>name</em> associated with it.
* @see MemoryLayout#name() * @see MemoryLayout#name()
*/ */
MemoryLayout withName(String name); MemoryLayout withName(String name);
@ -316,7 +312,7 @@ public interface MemoryLayout extends Constable {
* Creates a new layout which features the desired alignment constraint. * Creates a new layout which features the desired alignment constraint.
* *
* @param bitAlignment the layout alignment constraint, expressed in bits. * @param bitAlignment the layout alignment constraint, expressed in bits.
* @return a new layout which is the same as this layout, except for the alignment constraint associated to it. * @return a new layout which is the same as this layout, except for the alignment constraint associated with it.
* @throws IllegalArgumentException if {@code bitAlignment} is not a power of two, or if it's less than than 8. * @throws IllegalArgumentException if {@code bitAlignment} is not a power of two, or if it's less than than 8.
*/ */
MemoryLayout withBitAlignment(long bitAlignment); MemoryLayout withBitAlignment(long bitAlignment);
@ -357,6 +353,8 @@ public interface MemoryLayout extends Constable {
* layout path contains one or more path elements that select multiple sequence element indices * layout path contains one or more path elements that select multiple sequence element indices
* (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}). * (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}).
* @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size. * @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size.
* @throws NullPointerException if either {@code elements == null}, or if any of the elements
* in {@code elements} is {@code null}.
*/ */
default long bitOffset(PathElement... elements) { default long bitOffset(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::offset, return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::offset,
@ -380,8 +378,9 @@ public interface MemoryLayout extends Constable {
* }</pre></blockquote> * }</pre></blockquote>
* *
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long} * where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
* arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} and {@code s_0}, {@code s_1}, ... {@code s_n} are * arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are <em>static</em> offset constants
* <em>static</em> stride constants which are derived from the layout path. * and {@code s_0}, {@code s_1}, ... {@code s_n} are <em>static</em> stride constants which are derived from
* the layout path.
* *
* @param elements the layout path elements. * @param elements the layout path elements.
* @return a method handle that can be used to compute the bit offset of the layout element * @return a method handle that can be used to compute the bit offset of the layout element
@ -406,6 +405,8 @@ public interface MemoryLayout extends Constable {
* (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}). * (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}).
* @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size, * @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size,
* or if {@code bitOffset(elements)} is not a multiple of 8. * or if {@code bitOffset(elements)} is not a multiple of 8.
* @throws NullPointerException if either {@code elements == null}, or if any of the elements
* in {@code elements} is {@code null}.
*/ */
default long byteOffset(PathElement... elements) { default long byteOffset(PathElement... elements) {
return Utils.bitsToBytesOrThrow(bitOffset(elements), Utils.bitsToBytesThrowOffset); return Utils.bitsToBytesOrThrow(bitOffset(elements), Utils.bitsToBytesThrowOffset);
@ -429,8 +430,9 @@ public interface MemoryLayout extends Constable {
* }</pre></blockquote> * }</pre></blockquote>
* *
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long} * where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
* arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} and {@code s_0}, {@code s_1}, ... {@code s_n} are * arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are <em>static</em> offset constants
* <em>static</em> stride constants which are derived from the layout path. * and {@code s_0}, {@code s_1}, ... {@code s_n} are <em>static</em> stride constants which are derived from
* the layout path.
* *
* <p>The method handle will throw an {@link UnsupportedOperationException} if the computed * <p>The method handle will throw an {@link UnsupportedOperationException} if the computed
* offset in bits is not a multiple of 8. * offset in bits is not a multiple of 8.
@ -466,9 +468,10 @@ public interface MemoryLayout extends Constable {
offset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n) offset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
* }</pre></blockquote> * }</pre></blockquote>
* *
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as optional {@code long} * where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
* access coordinates, whereas {@code c_1}, {@code c_2}, ... {@code c_m} and {@code s_0}, {@code s_1}, ... {@code s_n} are * arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are <em>static</em> offset constants
* <em>static</em> stride constants which are derived from the layout path. * and {@code s_0}, {@code s_1}, ... {@code s_n} are <em>static</em> stride constants which are derived from
* the layout path.
* *
* @apiNote the resulting var handle will feature an additional {@code long} access coordinate for every * @apiNote the resulting var handle will feature an additional {@code long} access coordinate for every
* unspecified sequence access component contained in this layout path. Moreover, the resulting var handle * unspecified sequence access component contained in this layout path. Moreover, the resulting var handle
@ -489,6 +492,50 @@ public interface MemoryLayout extends Constable {
Set.of(), elements); Set.of(), elements);
} }
/**
* Creates a method handle which, given a memory segment, returns a {@linkplain MemorySegment#asSlice(long,long) slice}
* corresponding to the layout selected by a given layout path, where the path is considered rooted in this layout.
*
* <p>The returned method handle has a return type of {@code MemorySegment}, features a {@code MemorySegment}
* parameter as leading parameter representing the segment to be sliced, and features as many trailing {@code long}
* parameter types as there are free dimensions in the provided layout path (see {@link PathElement#sequenceElement()},
* where the order of the parameters corresponds to the order of the path elements.
* The returned method handle can be used to create a slice similar to using {@link MemorySegment#asSlice(long, long)},
* but where the offset argument is dynamically compute based on indices specified when invoking the method handle.
*
* <p>The offset of the returned segment is computed as follows:
*
* <blockquote><pre>{@code
bitOffset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
offset = bitOffset / 8
* }</pre></blockquote>
*
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
* arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are <em>static</em> offset constants
* and {@code s_0}, {@code s_1}, ... {@code s_n} are <em>static</em> stride constants which are derived from
* the layout path.
*
* <p>After the offset is computed, the returned segment is create as if by calling:
* <blockquote><pre>{@code
segment.asSlice(offset, layout.byteSize());
* }</pre></blockquote>
*
* where {@code segment} is the segment to be sliced, and where {@code layout} is the layout selected by the given
* layout path, as per {@link MemoryLayout#select(PathElement...)}.
*
* <p>The method handle will throw an {@link UnsupportedOperationException} if the computed
* offset in bits is not a multiple of 8.
*
* @param elements the layout path elements.
* @return a method handle which can be used to create a slice of the selected layout element, given a segment.
* @throws UnsupportedOperationException if the size of the selected layout in bits is not a multiple of 8.
*/
default MethodHandle sliceHandle(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::sliceHandle,
Set.of(), elements);
}
/** /**
* Selects the layout from a path rooted in this layout. * Selects the layout from a path rooted in this layout.
* *
@ -535,7 +582,7 @@ public interface MemoryLayout extends Constable {
} }
/** /**
* Is this a padding layout (e.g. a layout created from {@link #ofPaddingBits(long)}) ? * Is this a padding layout (e.g. a layout created from {@link #paddingLayout(long)}) ?
* @return true, if this layout is a padding layout. * @return true, if this layout is a padding layout.
*/ */
boolean isPadding(); boolean isPadding();
@ -554,14 +601,10 @@ public interface MemoryLayout extends Constable {
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null} * <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p> * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
* *
* @apiNote In the future, if the Java language permits, {@link PathElement}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*
* @implSpec * @implSpec
* Implementations of this interface are immutable and thread-safe. * Implementations of this interface are immutable and thread-safe.
*/ */
interface PathElement { sealed interface PathElement permits LayoutPath.PathElementImpl {
/** /**
* Returns a path element which selects a member layout with given name from a given group layout. * Returns a path element which selects a member layout with given name from a given group layout.
@ -679,7 +722,7 @@ E * (S + I * F)
* @return the new selector layout. * @return the new selector layout.
* @throws IllegalArgumentException if {@code size <= 0}. * @throws IllegalArgumentException if {@code size <= 0}.
*/ */
static MemoryLayout ofPaddingBits(long size) { static MemoryLayout paddingLayout(long size) {
AbstractLayout.checkSize(size); AbstractLayout.checkSize(size);
return new PaddingLayout(size); return new PaddingLayout(size);
} }
@ -692,7 +735,7 @@ E * (S + I * F)
* @return a new value layout. * @return a new value layout.
* @throws IllegalArgumentException if {@code size <= 0}. * @throws IllegalArgumentException if {@code size <= 0}.
*/ */
static ValueLayout ofValueBits(long size, ByteOrder order) { static ValueLayout valueLayout(long size, ByteOrder order) {
Objects.requireNonNull(order); Objects.requireNonNull(order);
AbstractLayout.checkSize(size); AbstractLayout.checkSize(size);
return new ValueLayout(order, size); return new ValueLayout(order, size);
@ -706,7 +749,7 @@ E * (S + I * F)
* @return the new sequence layout with given element layout and size. * @return the new sequence layout with given element layout and size.
* @throws IllegalArgumentException if {@code elementCount < 0}. * @throws IllegalArgumentException if {@code elementCount < 0}.
*/ */
static SequenceLayout ofSequence(long elementCount, MemoryLayout elementLayout) { static SequenceLayout sequenceLayout(long elementCount, MemoryLayout elementLayout) {
AbstractLayout.checkSize(elementCount, true); AbstractLayout.checkSize(elementCount, true);
OptionalLong size = OptionalLong.of(elementCount); OptionalLong size = OptionalLong.of(elementCount);
return new SequenceLayout(size, Objects.requireNonNull(elementLayout)); return new SequenceLayout(size, Objects.requireNonNull(elementLayout));
@ -718,7 +761,7 @@ E * (S + I * F)
* @param elementLayout the element layout of the sequence layout. * @param elementLayout the element layout of the sequence layout.
* @return the new sequence layout with given element layout. * @return the new sequence layout with given element layout.
*/ */
static SequenceLayout ofSequence(MemoryLayout elementLayout) { static SequenceLayout sequenceLayout(MemoryLayout elementLayout) {
return new SequenceLayout(OptionalLong.empty(), Objects.requireNonNull(elementLayout)); return new SequenceLayout(OptionalLong.empty(), Objects.requireNonNull(elementLayout));
} }
@ -728,7 +771,7 @@ E * (S + I * F)
* @param elements The member layouts of the <em>struct</em> group layout. * @param elements The member layouts of the <em>struct</em> group layout.
* @return a new <em>struct</em> group layout with given member layouts. * @return a new <em>struct</em> group layout with given member layouts.
*/ */
static GroupLayout ofStruct(MemoryLayout... elements) { static GroupLayout structLayout(MemoryLayout... elements) {
Objects.requireNonNull(elements); Objects.requireNonNull(elements);
return new GroupLayout(GroupLayout.Kind.STRUCT, return new GroupLayout(GroupLayout.Kind.STRUCT,
Stream.of(elements) Stream.of(elements)
@ -742,7 +785,7 @@ E * (S + I * F)
* @param elements The member layouts of the <em>union</em> layout. * @param elements The member layouts of the <em>union</em> layout.
* @return a new <em>union</em> group layout with given member layouts. * @return a new <em>union</em> group layout with given member layouts.
*/ */
static GroupLayout ofUnion(MemoryLayout... elements) { static GroupLayout unionLayout(MemoryLayout... elements) {
Objects.requireNonNull(elements); Objects.requireNonNull(elements);
return new GroupLayout(GroupLayout.Kind.UNION, return new GroupLayout(GroupLayout.Kind.UNION,
Stream.of(elements) Stream.of(elements)

View file

@ -46,87 +46,87 @@ public final class MemoryLayouts {
/** /**
* A value layout constant with size of one byte, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}. * A value layout constant with size of one byte, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}.
*/ */
public static final ValueLayout BITS_8_LE = MemoryLayout.ofValueBits(8, ByteOrder.LITTLE_ENDIAN); public static final ValueLayout BITS_8_LE = MemoryLayout.valueLayout(8, ByteOrder.LITTLE_ENDIAN);
/** /**
* A value layout constant with size of two bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}. * A value layout constant with size of two bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}.
*/ */
public static final ValueLayout BITS_16_LE = MemoryLayout.ofValueBits(16, ByteOrder.LITTLE_ENDIAN); public static final ValueLayout BITS_16_LE = MemoryLayout.valueLayout(16, ByteOrder.LITTLE_ENDIAN);
/** /**
* A value layout constant with size of four bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}. * A value layout constant with size of four bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}.
*/ */
public static final ValueLayout BITS_32_LE = MemoryLayout.ofValueBits(32, ByteOrder.LITTLE_ENDIAN); public static final ValueLayout BITS_32_LE = MemoryLayout.valueLayout(32, ByteOrder.LITTLE_ENDIAN);
/** /**
* A value layout constant with size of eight bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}. * A value layout constant with size of eight bytes, and byte order set to {@link ByteOrder#LITTLE_ENDIAN}.
*/ */
public static final ValueLayout BITS_64_LE = MemoryLayout.ofValueBits(64, ByteOrder.LITTLE_ENDIAN); public static final ValueLayout BITS_64_LE = MemoryLayout.valueLayout(64, ByteOrder.LITTLE_ENDIAN);
/** /**
* A value layout constant with size of one byte, and byte order set to {@link ByteOrder#BIG_ENDIAN}. * A value layout constant with size of one byte, and byte order set to {@link ByteOrder#BIG_ENDIAN}.
*/ */
public static final ValueLayout BITS_8_BE = MemoryLayout.ofValueBits(8, ByteOrder.BIG_ENDIAN); public static final ValueLayout BITS_8_BE = MemoryLayout.valueLayout(8, ByteOrder.BIG_ENDIAN);
/** /**
* A value layout constant with size of two bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}. * A value layout constant with size of two bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}.
*/ */
public static final ValueLayout BITS_16_BE = MemoryLayout.ofValueBits(16, ByteOrder.BIG_ENDIAN); public static final ValueLayout BITS_16_BE = MemoryLayout.valueLayout(16, ByteOrder.BIG_ENDIAN);
/** /**
* A value layout constant with size of four bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}. * A value layout constant with size of four bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}.
*/ */
public static final ValueLayout BITS_32_BE = MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN); public static final ValueLayout BITS_32_BE = MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN);
/** /**
* A value layout constant with size of eight bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}. * A value layout constant with size of eight bytes, and byte order set to {@link ByteOrder#BIG_ENDIAN}.
*/ */
public static final ValueLayout BITS_64_BE = MemoryLayout.ofValueBits(64, ByteOrder.BIG_ENDIAN); public static final ValueLayout BITS_64_BE = MemoryLayout.valueLayout(64, ByteOrder.BIG_ENDIAN);
/** /**
* A padding layout constant with size of one byte. * A padding layout constant with size of one byte.
*/ */
public static final MemoryLayout PAD_8 = MemoryLayout.ofPaddingBits(8); public static final MemoryLayout PAD_8 = MemoryLayout.paddingLayout(8);
/** /**
* A padding layout constant with size of two bytes. * A padding layout constant with size of two bytes.
*/ */
public static final MemoryLayout PAD_16 = MemoryLayout.ofPaddingBits(16); public static final MemoryLayout PAD_16 = MemoryLayout.paddingLayout(16);
/** /**
* A padding layout constant with size of four bytes. * A padding layout constant with size of four bytes.
*/ */
public static final MemoryLayout PAD_32 = MemoryLayout.ofPaddingBits(32); public static final MemoryLayout PAD_32 = MemoryLayout.paddingLayout(32);
/** /**
* A padding layout constant with size of eight bytes. * A padding layout constant with size of eight bytes.
*/ */
public static final MemoryLayout PAD_64 = MemoryLayout.ofPaddingBits(64); public static final MemoryLayout PAD_64 = MemoryLayout.paddingLayout(64);
/** /**
* A value layout constant whose size is the same as that of a machine address (e.g. {@code size_t}), and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a machine address (e.g. {@code size_t}), and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout ADDRESS = MemoryLayout.ofValueBits(Unsafe.ADDRESS_SIZE * 8, ByteOrder.nativeOrder()); public static final ValueLayout ADDRESS = MemoryLayout.valueLayout(Unsafe.ADDRESS_SIZE * 8, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code byte}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code byte}, and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout JAVA_BYTE = MemoryLayout.ofValueBits(8, ByteOrder.nativeOrder()); public static final ValueLayout JAVA_BYTE = MemoryLayout.valueLayout(8, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code char}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code char}, and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout JAVA_CHAR = MemoryLayout.ofValueBits(16, ByteOrder.nativeOrder()); public static final ValueLayout JAVA_CHAR = MemoryLayout.valueLayout(16, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code short}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code short}, and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout JAVA_SHORT = MemoryLayout.ofValueBits(16, ByteOrder.nativeOrder()); public static final ValueLayout JAVA_SHORT = MemoryLayout.valueLayout(16, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code int}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code int}, and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout JAVA_INT = MemoryLayout.ofValueBits(32, ByteOrder.nativeOrder()); public static final ValueLayout JAVA_INT = MemoryLayout.valueLayout(32, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code long}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code long}, and byte order set to {@link ByteOrder#nativeOrder()}.
@ -136,13 +136,13 @@ public final class MemoryLayouts {
MemoryLayouts.JAVA_LONG.byteAlignment() == MemoryLayouts.ADDRESS.byteSize(); MemoryLayouts.JAVA_LONG.byteAlignment() == MemoryLayouts.ADDRESS.byteSize();
* }</pre></blockquote> * }</pre></blockquote>
*/ */
public static final ValueLayout JAVA_LONG = MemoryLayout.ofValueBits(64, ByteOrder.nativeOrder()) public static final ValueLayout JAVA_LONG = MemoryLayout.valueLayout(64, ByteOrder.nativeOrder())
.withBitAlignment(ADDRESS.bitSize()); .withBitAlignment(ADDRESS.bitSize());
/** /**
* A value layout constant whose size is the same as that of a Java {@code float}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code float}, and byte order set to {@link ByteOrder#nativeOrder()}.
*/ */
public static final ValueLayout JAVA_FLOAT = MemoryLayout.ofValueBits(32, ByteOrder.nativeOrder()); public static final ValueLayout JAVA_FLOAT = MemoryLayout.valueLayout(32, ByteOrder.nativeOrder());
/** /**
* A value layout constant whose size is the same as that of a Java {@code double}, and byte order set to {@link ByteOrder#nativeOrder()}. * A value layout constant whose size is the same as that of a Java {@code double}, and byte order set to {@link ByteOrder#nativeOrder()}.
@ -152,6 +152,6 @@ public final class MemoryLayouts {
MemoryLayouts.JAVA_DOUBLE.byteAlignment() == MemoryLayouts.ADDRESS.byteSize(); MemoryLayouts.JAVA_DOUBLE.byteAlignment() == MemoryLayouts.ADDRESS.byteSize();
* }</pre></blockquote> * }</pre></blockquote>
*/ */
public static final ValueLayout JAVA_DOUBLE = MemoryLayout.ofValueBits(64, ByteOrder.nativeOrder()) public static final ValueLayout JAVA_DOUBLE = MemoryLayout.valueLayout(64, ByteOrder.nativeOrder())
.withBitAlignment(ADDRESS.bitSize()); .withBitAlignment(ADDRESS.bitSize());
} }

View file

@ -1,472 +0,0 @@
/*
* 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.incubator.foreign;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.AbstractNativeScope;
import jdk.internal.foreign.Utils;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Array;
import java.nio.ByteOrder;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* A native scope is an abstraction which provides shared temporal bounds for one or more allocations, backed
* by off-heap memory. Native scopes can be either <em>bounded</em> or <em>unbounded</em>, depending on whether the size
* of the native scope is known statically. If an application knows before-hand how much memory it needs to allocate,
* then using a <em>bounded</em> native scope will typically provide better performance than independently allocating the memory
* for each value (e.g. using {@link MemorySegment#allocateNative(long)}), or using an <em>unbounded</em> native scope.
* For this reason, using a bounded native scope is recommended in cases where programs might need to emulate native stack allocation.
* <p>
* Allocation scopes are thread-confined (see {@link #ownerThread()}; as such, the resulting {@link MemorySegment} instances
* returned by the native scope will be backed by memory segments confined by the same owner thread as the native scope's
* owner thread.
* <p>
* To allow for more usability, it is possible for a native scope to reclaim ownership of an existing memory segment
* (see {@link MemorySegment#handoff(NativeScope)}). This might be useful to allow one or more segments which were independently
* created to share the same life-cycle as a given native scope - which in turns enables a client to group all memory
* allocation and usage under a single <em>try-with-resources block</em>.
*
* <p> Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown. </p>
*
* @apiNote In the future, if the Java language permits, {@link NativeScope}
* may become a {@code sealed} interface, which would prohibit subclassing except by
* explicitly permitted types.
*/
public interface NativeScope extends AutoCloseable {
/**
* If this native scope is bounded, returns the size, in bytes, of this native scope.
* @return the size, in bytes, of this native scope (if available).
*/
OptionalLong byteSize();
/**
* The thread owning this native scope.
* @return the thread owning this native scope.
*/
Thread ownerThread();
/**
* Returns the number of allocated bytes in this native scope.
* @return the number of allocated bytes in this native scope.
*/
long allocatedBytes();
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given byte value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
*/
default MemorySegment allocate(ValueLayout layout, byte value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(byte.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given char value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a char value.
*/
default MemorySegment allocate(ValueLayout layout, char value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(char.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given short value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
*/
default MemorySegment allocate(ValueLayout layout, short value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(short.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given int value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
*/
default MemorySegment allocate(ValueLayout layout, int value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(int.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given float value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
*/
default MemorySegment allocate(ValueLayout layout, float value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(float.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given long value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
*/
default MemorySegment allocate(ValueLayout layout, long value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(long.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given double value.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
*/
default MemorySegment allocate(ValueLayout layout, double value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(double.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given address value
* (expressed as an {@link Addressable} instance).
* The address value might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
* The segment returned by this method cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
* @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
*/
default MemorySegment allocate(ValueLayout layout, Addressable value) {
Objects.requireNonNull(value);
Objects.requireNonNull(layout);
if (MemoryLayouts.ADDRESS.byteSize() != layout.byteSize()) {
throw new IllegalArgumentException("Layout size mismatch - " + layout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
}
switch ((int)layout.byteSize()) {
case 4: return allocate(layout, (int)value.address().toRawLongValue());
case 8: return allocate(layout, value.address().toRawLongValue());
default: throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
}
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given byte array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a byte value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, byte[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given short array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a short value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, short[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given char array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a char value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, char[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given int array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a int value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, int[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given float array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a float value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, float[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given long array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a long value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, long[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given double array.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a double value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, double[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory in this native scope with given layout and initialize it with given address array.
* The address value of each array element might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
* The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
* segment must conform to the layout alignment constraints.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
* @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, Addressable[] array) {
Objects.requireNonNull(elementLayout);
Objects.requireNonNull(array);
Stream.of(array).forEach(Objects::requireNonNull);
if (MemoryLayouts.ADDRESS.byteSize() != elementLayout.byteSize()) {
throw new IllegalArgumentException("Layout size mismatch - " + elementLayout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
}
switch ((int)elementLayout.byteSize()) {
case 4: return copyArrayWithSwapIfNeeded(Stream.of(array)
.mapToInt(a -> (int)a.address().toRawLongValue()).toArray(),
elementLayout, MemorySegment::ofArray);
case 8: return copyArrayWithSwapIfNeeded(Stream.of(array)
.mapToLong(a -> a.address().toRawLongValue()).toArray(),
elementLayout, MemorySegment::ofArray);
default: throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
}
}
private <Z> MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout,
Function<Z, MemorySegment> heapSegmentFactory) {
Objects.requireNonNull(array);
Objects.requireNonNull(elementLayout);
Utils.checkPrimitiveCarrierCompat(array.getClass().componentType(), elementLayout);
MemorySegment addr = allocate(MemoryLayout.ofSequence(Array.getLength(array), elementLayout));
if (elementLayout.byteSize() == 1 || (elementLayout.order() == ByteOrder.nativeOrder())) {
addr.copyFrom(heapSegmentFactory.apply(array));
} else {
((AbstractMemorySegmentImpl)addr).copyFromSwap(heapSegmentFactory.apply(array), elementLayout.byteSize());
}
return addr;
}
/**
* Allocate a block of memory in this native scope with given layout. The segment returned by this method is
* associated with a segment which cannot be closed. Moreover, the returned segment must conform to the layout alignment constraints.
* @param layout the layout of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
*/
default MemorySegment allocate(MemoryLayout layout) {
Objects.requireNonNull(layout);
return allocate(layout.byteSize(), layout.byteAlignment());
}
/**
* Allocate a block of memory corresponding to an array with given element layout and size.
* The segment returned by this method is associated with a segment which cannot be closed.
* Moreover, the returned segment must conform to the layout alignment constraints. This is equivalent to the
* following code:
* <pre>{@code
allocate(MemoryLayout.ofSequence(size, elementLayout));
* }</pre>
* @param elementLayout the array element layout.
* @param count the array element count.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
* bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * count)}.
*/
default MemorySegment allocateArray(MemoryLayout elementLayout, long count) {
Objects.requireNonNull(elementLayout);
return allocate(MemoryLayout.ofSequence(count, elementLayout));
}
/**
* Allocate a block of memory in this native scope with given size. The segment returned by this method is
* associated with a segment which cannot be closed. Moreover, the returned segment must be aligned to {@code size}.
* @param bytesSize the size (in bytes) of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
* {@code limit() - size() < bytesSize}.
*/
default MemorySegment allocate(long bytesSize) {
return allocate(bytesSize, bytesSize);
}
/**
* Allocate a block of memory in this native scope with given size and alignment constraint.
* The segment returned by this method is associated with a segment which cannot be closed. Moreover,
* the returned segment must be aligned to {@code alignment}.
* @param bytesSize the size (in bytes) of the block of memory to be allocated.
* @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
* @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
* {@code limit() - size() < bytesSize}.
*/
MemorySegment allocate(long bytesSize, long bytesAlignment);
/**
* Close this native scope; calling this method will render any segment obtained through this native scope
* unusable and might release any backing memory resources associated with this native scope.
*/
@Override
void close();
/**
* Creates a new bounded native scope, backed by off-heap memory.
* @param size the size of the native scope.
* @return a new bounded native scope, with given size (in bytes).
*/
static NativeScope boundedScope(long size) {
return new AbstractNativeScope.BoundedNativeScope(size);
}
/**
* Creates a new unbounded native scope, backed by off-heap memory.
* @return a new unbounded native scope.
*/
static NativeScope unboundedScope() {
return new AbstractNativeScope.UnboundedNativeScope();
}
}

View file

@ -0,0 +1,285 @@
/*
* Copyright (c) 2021, 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.incubator.foreign;
import jdk.internal.foreign.ResourceScopeImpl;
import java.lang.invoke.MethodHandle;
import java.lang.ref.Cleaner;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Spliterator;
/**
* A resource scope manages the lifecycle of one or more resources. Resources (e.g. {@link MemorySegment}) associated
* with a resource scope can only be accessed while the resource scope is <em>alive</em> (see {@link #isAlive()}),
* and by the thread associated with the resource scope (if any).
*
* <h2>Explicit resource scopes</h2>
*
* Resource scopes obtained from {@link #newConfinedScope()}, {@link #newSharedScope()} support <em>deterministic deallocation</em>;
* We call these resource scopes <em>explicit scopes</em>. Explicit resource scopes can be closed explicitly (see {@link ResourceScope#close()}).
* When a resource scope is closed, it is no longer <em>alive</em> (see {@link #isAlive()}, and subsequent operations on
* resources associated with that scope (e.g. attempting to access a {@link MemorySegment} instance) will fail with {@link IllegalStateException}.
* <p>
* Closing a resource scope will cause all the cleanup actions associated with that scope (see {@link #addCloseAction(Runnable)}) to be called.
* Moreover, closing a resource scope might trigger the releasing of the underlying memory resources associated with said scope; for instance:
* <ul>
* <li>closing the scope associated with a native memory segment results in <em>freeing</em> the native memory associated with it
* (see {@link MemorySegment#allocateNative(long, ResourceScope)}, or {@link SegmentAllocator#arenaAllocator(ResourceScope)})</li>
* <li>closing the scope associated with a mapped memory segment results in the backing memory-mapped file to be unmapped
* (see {@link MemorySegment#mapFile(Path, long, long, FileChannel.MapMode, ResourceScope)})</li>
* <li>closing the scope associated with an upcall stub results in releasing the stub
* (see {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor, ResourceScope)}</li>
* </ul>
* <p>
* Sometimes, explicit scopes can be associated with a {@link Cleaner} instance (see {@link #newConfinedScope(Cleaner)} and
* {@link #newSharedScope(Cleaner)}). We call these resource scopes <em>managed</em> resource scopes. A managed resource scope
* is closed automatically once the scope instance becomes <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>.
* <p>
* Managed scopes can be useful to allow for predictable, deterministic resource deallocation, while still prevent accidental native memory leaks.
* In case a managed resource scope is closed explicitly, no further action will be taken when the scope becomes unreachable;
* that is, cleanup actions (see {@link #addCloseAction(Runnable)}) associated with a resource scope, whether managed or not,
* are called <em>exactly once</em>.
*
* <h2>Implicit resource scopes</h2>
*
* Resource scopes obtained from {@link #newImplicitScope()} cannot be closed explicitly. We call these resource scopes
* <em>implicit scopes</em>. Calling {@link #close()} on an implicit resource scope always results in an exception.
* Resources associated with implicit scopes are released once the scope instance becomes
* <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>.
* <p>
* An important implicit resource scope is the so called {@linkplain #globalScope() global scope}; the global scope is
* an implicit scope that is guaranteed to never become <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>.
* As a results, the global scope will never attempt to release resources associated with it. Such resources must, where
* needed, be managed independently by clients.
*
* <h2><a id = "thread-confinement">Thread confinement</a></h2>
*
* Resource scopes can be further divided into two categories: <em>thread-confined</em> resource scopes, and <em>shared</em>
* resource scopes.
* <p>
* Confined resource scopes (see {@link #newConfinedScope()}), support strong thread-confinement guarantees. Upon creation,
* they are assigned an <em>owner thread</em>, typically the thread which initiated the creation operation (see {@link #ownerThread()}).
* After creating a confined resource scope, only the owner thread will be allowed to directly manipulate the resources
* associated with this resource scope. Any attempt to perform resource access from a thread other than the
* owner thread will result in a runtime failure.
* <p>
* Shared resource scopes (see {@link #newSharedScope()} and {@link #newImplicitScope()}), on the other hand, have no owner thread;
* as such resources associated with this shared resource scopes can be accessed by multiple threads.
* This might be useful when multiple threads need to access the same resource concurrently (e.g. in the case of parallel processing).
* For instance, a client might obtain a {@link Spliterator} from a shared segment, which can then be used to slice the
* segment and allow multiple threads to work in parallel on disjoint segment slices. The following code can be used to sum
* all int values in a memory segment in parallel:
*
* <blockquote><pre>{@code
SequenceLayout SEQUENCE_LAYOUT = MemoryLayout.sequenceLayout(1024, MemoryLayouts.JAVA_INT);
try (ResourceScope scope = ResourceScope.newSharedScope()) {
MemorySegment segment = MemorySegment.allocateNative(SEQUENCE_LAYOUT, scope);
VarHandle VH_int = SEQUENCE_LAYOUT.elementLayout().varHandle(int.class);
int sum = StreamSupport.stream(segment.spliterator(SEQUENCE_LAYOUT), true)
.mapToInt(s -> (int)VH_int.get(s.address()))
.sum();
}
* }</pre></blockquote>
*
* <p>
* Explicit shared resource scopes, while powerful, must be used with caution: if one or more threads accesses
* a resource associated with a shared scope while the scope is being closed from another thread, an exception might occur on both
* the accessing and the closing threads. Clients should refrain from attempting to close a shared resource scope repeatedly
* (e.g. keep calling {@link #close()} until no exception is thrown). Instead, clients of shared resource scopes
* should always ensure that proper synchronization mechanisms (e.g. using resource scope handles, see below) are put in place
* so that threads closing shared resource scopes can never race against threads accessing resources managed by same scopes.
*
* <h2>Resource scope handles</h2>
*
* Resource scopes can be made <em>non-closeable</em> by acquiring one or more resource scope <em>handles</em> (see
* {@link #acquire()}. A resource scope handle can be used to make sure that resources associated with a given resource scope
* (either explicit or implicit) cannot be released for a certain period of time - e.g. during a critical region of code
* involving one or more resources associated with the scope. For instance, an explicit resource scope can only be closed
* <em>after</em> all the handles acquired against that scope have been closed (see {@link Handle#close()}).
* This can be useful when clients need to perform a critical operation on a memory segment, during which they have
* to ensure that the segment will not be released; this can be done as follows:
*
* <blockquote><pre>{@code
MemorySegment segment = ...
ResourceScope.Handle segmentHandle = segment.scope().acquire()
try {
<critical operation on segment>
} finally {
segment.scope().release(segmentHandle);
}
* }</pre></blockquote>
*
* Acquiring implicit resource scopes is also possible, but it is often unnecessary: since resources associated with
* an implicit scope will only be released when the scope becomes <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>,
* clients can use e.g. {@link java.lang.ref.Reference#reachabilityFence(Object)} to make sure that resources associated
* with implicit scopes are not released prematurely. That said, the above code snippet works (trivially) for implicit scopes too.
*
* @implSpec
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
*/
public sealed interface ResourceScope extends AutoCloseable permits ResourceScopeImpl {
/**
* Is this resource scope alive?
* @return true, if this resource scope is alive.
* @see ResourceScope#close()
*/
boolean isAlive();
/**
* The thread owning this resource scope.
* @return the thread owning this resource scope, or {@code null} if this resource scope is shared.
*/
Thread ownerThread();
/**
* Is this resource scope an <em>implicit scope</em>?
* @return true if this scope is an <em>implicit scope</em>.
* @see #newImplicitScope()
* @see #globalScope()
*/
boolean isImplicit();
/**
* Closes this resource scope. As a side-effect, if this operation completes without exceptions, this scope will be marked
* as <em>not alive</em>, and subsequent operations on resources associated with this scope will fail with {@link IllegalStateException}.
* Additionally, upon successful closure, all native resources associated with this resource scope will be released.
*
* @apiNote This operation is not idempotent; that is, closing an already closed resource scope <em>always</em> results in an
* exception being thrown. This reflects a deliberate design choice: resource scope state transitions should be
* manifest in the client code; a failure in any of these transitions reveals a bug in the underlying application
* logic.
*
* @throws IllegalStateException if one of the following condition is met:
* <ul>
* <li>this resource scope is not <em>alive</em>
* <li>this resource scope is confined, and this method is called from a thread other than the thread owning this resource scope</li>
* <li>this resource scope is shared and a resource associated with this scope is accessed while this method is called</li>
* <li>one or more handles (see {@link #acquire()}) associated with this resource scope have not been {@linkplain #release(Handle) released}</li>
* </ul>
* @throws UnsupportedOperationException if this resource scope is {@linkplain #isImplicit() implicit}.
*/
void close();
/**
* Add a custom cleanup action which will be executed when the resource scope is closed.
* The order in which custom cleanup actions are invoked once the scope is closed is unspecified.
* @param runnable the custom cleanup action to be associated with this scope.
* @throws IllegalStateException if this scope has already been closed.
*/
void addCloseAction(Runnable runnable);
/**
* Acquires a resource scope handle associated with this resource scope. An explicit resource scope cannot be
* {@linkplain #close() closed} until all the resource scope handles acquired from it have been {@linkplain #release(Handle)} released}.
* @return a resource scope handle.
*/
Handle acquire();
/**
* Release the provided resource scope handle. This method is idempotent, that is, releasing the same handle
* multiple times has no effect.
* @param handle the resource scope handle to be released.
* @throws IllegalArgumentException if the provided handle is not associated with this scope.
*/
void release(Handle handle);
/**
* An abstraction modelling a resource scope handle. A resource scope handle is typically {@linkplain #acquire() acquired} by clients
* in order to prevent an explicit resource scope from being closed while executing a certain operation.
* Once obtained, resource scope handles can be {@linkplain #release(Handle)} released}; an explicit resource scope can
* be closed only <em>after</em> all the resource scope handles acquired from it have been released.
*/
sealed interface Handle permits ResourceScopeImpl.HandleImpl {
/**
* Returns the resource scope associated with this handle.
* @return the resource scope associated with this handle.
*/
ResourceScope scope();
}
/**
* Create a new confined scope. The resulting scope is closeable, and is not managed by a {@link Cleaner}.
* @return a new confined scope.
*/
static ResourceScope newConfinedScope() {
return ResourceScopeImpl.createConfined( null);
}
/**
* Create a new confined scope managed by a {@link Cleaner}.
* @param cleaner the cleaner to be associated with the returned scope.
* @return a new confined scope, managed by {@code cleaner}.
* @throws NullPointerException if {@code cleaner == null}.
*/
static ResourceScope newConfinedScope(Cleaner cleaner) {
Objects.requireNonNull(cleaner);
return ResourceScopeImpl.createConfined( cleaner);
}
/**
* Create a new shared scope. The resulting scope is closeable, and is not managed by a {@link Cleaner}.
* @return a new shared scope.
*/
static ResourceScope newSharedScope() {
return ResourceScopeImpl.createShared(null);
}
/**
* Create a new shared scope managed by a {@link Cleaner}.
* @param cleaner the cleaner to be associated with the returned scope.
* @return a new shared scope, managed by {@code cleaner}.
* @throws NullPointerException if {@code cleaner == null}.
*/
static ResourceScope newSharedScope(Cleaner cleaner) {
Objects.requireNonNull(cleaner);
return ResourceScopeImpl.createShared(cleaner);
}
/**
* Create a new <em>implicit scope</em>. The implicit scope is a managed, shared, and non-closeable scope which only features
* <a href="ResourceScope.html#implicit-closure"><em>implicit closure</em></a>.
* Since implicit scopes can only be closed implicitly by the garbage collector, it is recommended that implicit
* scopes are only used in cases where deallocation performance is not a critical concern, to avoid unnecessary
* memory pressure.
*
* @return a new implicit scope.
*/
static ResourceScope newImplicitScope() {
return ResourceScopeImpl.createImplicitScope();
}
/**
* Returns an implicit scope which is assumed to be always alive.
* @return the global scope.
*/
static ResourceScope globalScope() {
return ResourceScopeImpl.GLOBAL;
}
}

View file

@ -0,0 +1,466 @@
/*
* Copyright (c) 2021, 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.incubator.foreign;
import jdk.internal.foreign.ArenaAllocator;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.ResourceScopeImpl;
import jdk.internal.foreign.Utils;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Array;
import java.nio.ByteOrder;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* This interface models a memory allocator. Clients implementing this interface
* must implement the {@link #allocate(long, long)} method. This interface defines several default methods
* which can be useful to create segments from several kinds of Java values such as primitives and arrays.
* This interface can be seen as a thin wrapper around the basic capabilities for creating native segments
* (e.g. {@link MemorySegment#allocateNative(long, long, ResourceScope)}); since {@link SegmentAllocator} is a <em>functional interface</em>,
* clients can easily obtain a native allocator by using either a lambda expression or a method reference.
* <p>
* This interface provides a factory, namely {@link SegmentAllocator#ofScope(ResourceScope)} which can be used to obtain
* a <em>scoped</em> allocator, that is, an allocator which creates segment bound by a given scope. This can be useful
* when working inside a <em>try-with-resources</em> construct:
*
* <blockquote><pre>{@code
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.ofScope(scope);
...
}
* }</pre></blockquote>
*
* In addition, this interface also defines factories for commonly used allocators; for instance {@link #arenaAllocator(ResourceScope)}
* and {@link #arenaAllocator(long, ResourceScope)} are arena-style native allocators. Finally {@link #ofSegment(MemorySegment)}
* returns an allocator which wraps a segment (either on-heap or off-heap) and recycles its content upon each new allocation request.
*/
@FunctionalInterface
public interface SegmentAllocator {
/**
* Allocate a block of memory with given layout and initialize it with given byte value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
*/
default MemorySegment allocate(ValueLayout layout, byte value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(byte.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given char value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a char value.
*/
default MemorySegment allocate(ValueLayout layout, char value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(char.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given short value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
*/
default MemorySegment allocate(ValueLayout layout, short value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(short.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given int value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
*/
default MemorySegment allocate(ValueLayout layout, int value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(int.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given float value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
*/
default MemorySegment allocate(ValueLayout layout, float value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(float.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given long value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
*/
default MemorySegment allocate(ValueLayout layout, long value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(long.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given double value.
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
*/
default MemorySegment allocate(ValueLayout layout, double value) {
Objects.requireNonNull(layout);
VarHandle handle = layout.varHandle(double.class);
MemorySegment addr = allocate(layout);
handle.set(addr, value);
return addr;
}
/**
* Allocate a block of memory with given layout and initialize it with given address value
* (expressed as an {@link Addressable} instance).
* The address value might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
* @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
* @param layout the layout of the block of memory to be allocated.
* @param value the value to be set on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
*/
default MemorySegment allocate(ValueLayout layout, Addressable value) {
Objects.requireNonNull(value);
Objects.requireNonNull(layout);
if (MemoryLayouts.ADDRESS.byteSize() != layout.byteSize()) {
throw new IllegalArgumentException("Layout size mismatch - " + layout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
}
return switch ((int)layout.byteSize()) {
case 4 -> allocate(layout, (int)value.address().toRawLongValue());
case 8 -> allocate(layout, value.address().toRawLongValue());
default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
};
}
/**
* Allocate a block of memory with given layout and initialize it with given byte array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a byte value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, byte[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given short array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a short value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, short[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given char array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a char value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, char[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given int array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a int value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, int[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given float array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a float value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, float[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given long array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a long value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, long[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given double array.
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a double value.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, double[] array) {
return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
}
/**
* Allocate a block of memory with given layout and initialize it with given address array.
* The address value of each array element might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
* @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
* @param elementLayout the element layout of the array to be allocated.
* @param array the array to be copied on the newly allocated memory block.
* @return a segment for the newly allocated memory block.
* @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
*/
default MemorySegment allocateArray(ValueLayout elementLayout, Addressable[] array) {
Objects.requireNonNull(elementLayout);
Objects.requireNonNull(array);
Stream.of(array).forEach(Objects::requireNonNull);
if (MemoryLayouts.ADDRESS.byteSize() != elementLayout.byteSize()) {
throw new IllegalArgumentException("Layout size mismatch - " + elementLayout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
}
return switch ((int)elementLayout.byteSize()) {
case 4 -> copyArrayWithSwapIfNeeded(Stream.of(array)
.mapToInt(a -> (int)a.address().toRawLongValue()).toArray(),
elementLayout, MemorySegment::ofArray);
case 8 -> copyArrayWithSwapIfNeeded(Stream.of(array)
.mapToLong(a -> a.address().toRawLongValue()).toArray(),
elementLayout, MemorySegment::ofArray);
default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
};
}
private <Z> MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout,
Function<Z, MemorySegment> heapSegmentFactory) {
Objects.requireNonNull(array);
Objects.requireNonNull(elementLayout);
Utils.checkPrimitiveCarrierCompat(array.getClass().componentType(), elementLayout);
MemorySegment addr = allocate(MemoryLayout.sequenceLayout(Array.getLength(array), elementLayout));
if (elementLayout.byteSize() == 1 || (elementLayout.order() == ByteOrder.nativeOrder())) {
addr.copyFrom(heapSegmentFactory.apply(array));
} else {
((AbstractMemorySegmentImpl)addr).copyFromSwap(heapSegmentFactory.apply(array), elementLayout.byteSize());
}
return addr;
}
/**
* Allocate a block of memory with given layout.
* @implSpec the default implementation for this method calls {@code this.allocate(layout.byteSize(), layout.byteAlignment())}.
* @param layout the layout of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
*/
default MemorySegment allocate(MemoryLayout layout) {
Objects.requireNonNull(layout);
return allocate(layout.byteSize(), layout.byteAlignment());
}
/**
* Allocate a block of memory corresponding to an array with given element layout and size.
* @implSpec the default implementation for this method calls {@code this.allocate(MemoryLayout.sequenceLayout(count, elementLayout))}.
* @param elementLayout the array element layout.
* @param count the array element count.
* @return a segment for the newly allocated memory block.
*/
default MemorySegment allocateArray(MemoryLayout elementLayout, long count) {
Objects.requireNonNull(elementLayout);
return allocate(MemoryLayout.sequenceLayout(count, elementLayout));
}
/**
* Allocate a block of memory with given size, with default alignment (1-byte aligned).
* @implSpec the default implementation for this method calls {@code this.allocate(bytesSize, 1)}.
* @param bytesSize the size (in bytes) of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
*/
default MemorySegment allocate(long bytesSize) {
return allocate(bytesSize, 1);
}
/**
* Allocate a block of memory with given size and alignment constraint.
* @param bytesSize the size (in bytes) of the block of memory to be allocated.
* @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
* @return a segment for the newly allocated memory block.
*/
MemorySegment allocate(long bytesSize, long bytesAlignment);
/**
* Returns a native arena-based allocator which allocates a single memory segment, of given size (using malloc),
* and then responds to allocation request by returning different slices of that same segment
* (until no further allocation is possible).
* This can be useful when clients want to perform multiple allocation requests while avoiding the cost associated
* with allocating a new off-heap memory region upon each allocation request.
* <p>
* An allocator associated with a <em>shared</em> resource scope is thread-safe and allocation requests may be
* performed concurrently; conversely, if the arena allocator is associated with a <em>confined</em> resource scope,
* allocation requests can only occur from the thread owning the allocator's resource scope.
* <p>
* The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
* the allocator capacity.
*
* @param size the size (in bytes) of the allocation arena.
* @param scope the scope associated with the segments returned by this allocator.
* @return a new bounded arena-based allocator
* @throws IllegalArgumentException if {@code size <= 0}.
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/
static SegmentAllocator arenaAllocator(long size, ResourceScope scope) {
Objects.requireNonNull(scope);
return scope.ownerThread() == null ?
new ArenaAllocator.BoundedSharedArenaAllocator(scope, size) :
new ArenaAllocator.BoundedArenaAllocator(scope, size);
}
/**
* Returns a native unbounded arena-based allocator.
* <p>
* The returned allocator allocates a memory segment {@code S} of a certain fixed size (using malloc) and then
* responds to allocation requests in one of the following ways:
* <ul>
* <li>if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has a <em>free</em>
* slice {@code S'} which fits that allocation request, return that {@code S'}.
* <li>if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has no <em>free</em>
* slices which fits that allocation request, allocate a new segment {@code S'} (using malloc), which has same size as {@code S}
* and set {@code S = S'}; the allocator then tries to respond to the same allocation request again.
* <li>if the size of the allocation requests is bigger than the size of {@code S}, allocate a new segment {@code S'}
* (using malloc), which has a sufficient size to satisfy the allocation request, and return {@code S'}.
* </ul>
* <p>
* This segment allocator can be useful when clients want to perform multiple allocation requests while avoiding the
* cost associated with allocating a new off-heap memory region upon each allocation request.
* <p>
* An allocator associated with a <em>shared</em> resource scope is thread-safe and allocation requests may be
* performed concurrently; conversely, if the arena allocator is associated with a <em>confined</em> resource scope,
* allocation requests can only occur from the thread owning the allocator's resource scope.
* <p>
* The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
* the system capacity.
*
* @param scope the scope associated with the segments returned by this allocator.
* @return a new unbounded arena-based allocator
* @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
* than the thread owning {@code scope}.
*/
static SegmentAllocator arenaAllocator(ResourceScope scope) {
Objects.requireNonNull(scope);
return scope.ownerThread() == null ?
new ArenaAllocator.UnboundedSharedArenaAllocator(scope) :
new ArenaAllocator.UnboundedArenaAllocator(scope);
}
/**
* Returns a segment allocator which responds to allocation requests by recycling a single segment; that is,
* each new allocation request will return a new slice starting at the segment offset {@code 0} (alignment
* constraints are ignored by this allocator). This can be useful to limit allocation requests in case a client
* knows that they have fully processed the contents of the allocated segment before the subsequent allocation request
* takes place.
* <p>
* While the allocator returned by this method is <em>thread-safe</em>, concurrent access on the same recycling
* allocator might cause a thread to overwrite contents written to the underlying segment by a different thread.
*
* @param segment the memory segment to be recycled by the returned allocator.
* @return an allocator which recycles an existing segment upon each new allocation request.
*/
static SegmentAllocator ofSegment(MemorySegment segment) {
Objects.requireNonNull(segment);
return (size, align) -> segment.asSlice(0, size);
}
/**
* Returns a native allocator which responds to allocation requests by allocating new segments
* bound by the given resource scope, using the {@link MemorySegment#allocateNative(long, long, ResourceScope)}
* factory. This code is equivalent (but likely more efficient) to the following:
* <blockquote><pre>{@code
Resource scope = ...
SegmentAllocator scoped = (size, align) -> MemorySegment.allocateNative(size, align, scope);
* }</pre></blockquote>
*
* @param scope the resource scope associated with the segments created by the returned allocator.
* @return an allocator which allocates new memory segment bound by the provided resource scope.
*/
static SegmentAllocator ofScope(ResourceScope scope) {
Objects.requireNonNull(scope);
return (ResourceScopeImpl)scope;
}
}

View file

@ -32,7 +32,6 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.OptionalLong; import java.util.OptionalLong;
import java.util.stream.LongStream;
/** /**
* A sequence layout. A sequence layout is used to denote a repetition of a given layout, also called the sequence layout's <em>element layout</em>. * A sequence layout. A sequence layout is used to denote a repetition of a given layout, also called the sequence layout's <em>element layout</em>.
@ -41,16 +40,16 @@ import java.util.stream.LongStream;
* that is equal to the sequence layout's element count. In other words this layout: * that is equal to the sequence layout's element count. In other words this layout:
* *
* <pre>{@code * <pre>{@code
MemoryLayout.ofSequence(3, MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN)); MemoryLayout.sequenceLayout(3, MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN));
* }</pre> * }</pre>
* *
* is equivalent to the following layout: * is equivalent to the following layout:
* *
* <pre>{@code * <pre>{@code
MemoryLayout.ofStruct( MemoryLayout.structLayout(
MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN), MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN),
MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN), MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN),
MemoryLayout.ofValueBits(32, ByteOrder.BIG_ENDIAN)); MemoryLayout.valueLayout(32, ByteOrder.BIG_ENDIAN));
* }</pre> * }</pre>
* *
* <p> * <p>
@ -67,7 +66,7 @@ MemoryLayout.ofStruct(
* @implSpec * @implSpec
* This class is immutable and thread-safe. * This class is immutable and thread-safe.
*/ */
public final class SequenceLayout extends AbstractLayout { public final class SequenceLayout extends AbstractLayout implements MemoryLayout {
private final OptionalLong elemCount; private final OptionalLong elemCount;
private final MemoryLayout elementLayout; private final MemoryLayout elementLayout;
@ -123,11 +122,11 @@ public final class SequenceLayout extends AbstractLayout {
* <p> * <p>
* For instance, given a sequence layout of the kind: * For instance, given a sequence layout of the kind:
* <pre>{@code * <pre>{@code
var seq = MemoryLayout.ofSequence(4, MemoryLayout.ofSequence(3, MemoryLayouts.JAVA_INT)); var seq = MemoryLayout.sequenceLayout(4, MemoryLayout.sequenceLayout(3, MemoryLayouts.JAVA_INT));
* }</pre> * }</pre>
* calling {@code seq.reshape(2, 6)} will yield the following sequence layout: * calling {@code seq.reshape(2, 6)} will yield the following sequence layout:
* <pre>{@code * <pre>{@code
var reshapeSeq = MemoryLayout.ofSequence(2, MemoryLayout.ofSequence(6, MemoryLayouts.JAVA_INT)); var reshapeSeq = MemoryLayout.sequenceLayout(2, MemoryLayout.sequenceLayout(6, MemoryLayouts.JAVA_INT));
* }</pre> * }</pre>
* <p> * <p>
* If one of the provided element count is the special value {@code -1}, then the element * If one of the provided element count is the special value {@code -1}, then the element
@ -187,7 +186,7 @@ public final class SequenceLayout extends AbstractLayout {
MemoryLayout res = flat.elementLayout(); MemoryLayout res = flat.elementLayout();
for (int i = elementCounts.length - 1 ; i >= 0 ; i--) { for (int i = elementCounts.length - 1 ; i >= 0 ; i--) {
res = MemoryLayout.ofSequence(elementCounts[i], res); res = MemoryLayout.sequenceLayout(elementCounts[i], res);
} }
return (SequenceLayout)res; return (SequenceLayout)res;
} }
@ -199,11 +198,11 @@ public final class SequenceLayout extends AbstractLayout {
* be dropped and their element counts will be incorporated into that of the returned sequence layout. * be dropped and their element counts will be incorporated into that of the returned sequence layout.
* For instance, given a sequence layout of the kind: * For instance, given a sequence layout of the kind:
* <pre>{@code * <pre>{@code
var seq = MemoryLayout.ofSequence(4, MemoryLayout.ofSequence(3, MemoryLayouts.JAVA_INT)); var seq = MemoryLayout.sequenceLayout(4, MemoryLayout.sequenceLayout(3, MemoryLayouts.JAVA_INT));
* }</pre> * }</pre>
* calling {@code seq.flatten()} will yield the following sequence layout: * calling {@code seq.flatten()} will yield the following sequence layout:
* <pre>{@code * <pre>{@code
var flattenedSeq = MemoryLayout.ofSequence(12, MemoryLayouts.JAVA_INT); var flattenedSeq = MemoryLayout.sequenceLayout(12, MemoryLayouts.JAVA_INT);
* }</pre> * }</pre>
* @return a new sequence layout with the same size as this layout (but, possibly, with different * @return a new sequence layout with the same size as this layout (but, possibly, with different
* element count), whose element layout is not a sequence layout. * element count), whose element layout is not a sequence layout.
@ -221,7 +220,7 @@ public final class SequenceLayout extends AbstractLayout {
count = count * elemSeq.elementCount().orElseThrow(this::badUnboundSequenceLayout); count = count * elemSeq.elementCount().orElseThrow(this::badUnboundSequenceLayout);
elemLayout = elemSeq.elementLayout(); elemLayout = elemSeq.elementLayout();
} }
return MemoryLayout.ofSequence(count, elemLayout); return MemoryLayout.sequenceLayout(count, elemLayout);
} }
private UnsupportedOperationException badUnboundSequenceLayout() { private UnsupportedOperationException badUnboundSequenceLayout() {

View file

@ -45,19 +45,15 @@
* ranging from {@code 0} to {@code 9}, we can use the following code: * ranging from {@code 0} to {@code 9}, we can use the following code:
* *
* <pre>{@code * <pre>{@code
try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) { MemorySegment segment = MemorySegment.allocateNative(10 * 4, ResourceScope.newImplicitScope());
for (int i = 0 ; i < 10 ; i++) { for (int i = 0 ; i < 10 ; i++) {
MemoryAccess.setIntAtIndex(segment, i); MemoryAccess.setIntAtIndex(segment, i, 42);
}
} }
* }</pre> * }</pre>
* *
* Here create a <em>native</em> memory segment, that is, a memory segment backed by * Here create a <em>native</em> memory segment, that is, a memory segment backed by
* off-heap memory; the size of the segment is 40 bytes, enough to store 10 values of the primitive type {@code int}. * off-heap memory; the size of the segment is 40 bytes, enough to store 10 values of the primitive type {@code int}.
* The segment is created inside a <em>try-with-resources</em> construct: this idiom ensures that all the memory resources * Inside a loop, we then initialize the contents of the memory segment using the
* associated with the segment will be released at the end of the block, according to the semantics described in
* Section {@jls 14.20.3} of <cite>The Java Language Specification</cite>. Inside the try-with-resources block, we initialize
* the contents of the memory segment using the
* {@link jdk.incubator.foreign.MemoryAccess#setIntAtIndex(jdk.incubator.foreign.MemorySegment, long, int)} helper method; * {@link jdk.incubator.foreign.MemoryAccess#setIntAtIndex(jdk.incubator.foreign.MemorySegment, long, int)} helper method;
* more specifically, if we view the memory segment as a set of 10 adjacent slots, * more specifically, if we view the memory segment as a set of 10 adjacent slots,
* {@code s[i]}, where {@code 0 <= i < 10}, where the size of each slot is exactly 4 bytes, the initialization logic above will set each slot * {@code s[i]}, where {@code 0 <= i < 10}, where the size of each slot is exactly 4 bytes, the initialization logic above will set each slot
@ -66,16 +62,25 @@ try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) {
* <h3><a id="deallocation"></a>Deterministic deallocation</h3> * <h3><a id="deallocation"></a>Deterministic deallocation</h3>
* *
* When writing code that manipulates memory segments, especially if backed by memory which resides outside the Java heap, it is * When writing code that manipulates memory segments, especially if backed by memory which resides outside the Java heap, it is
* crucial that the resources associated with a memory segment are released when the segment is no longer in use, by calling the {@link jdk.incubator.foreign.MemorySegment#close()} * often crucial that the resources associated with a memory segment are released when the segment is no longer in use,
* method either explicitly, or implicitly, by relying on try-with-resources construct (as demonstrated in the example above). * and in a timely fashion. For this reason, there might be cases where waiting for the garbage collector to determine that a segment
* Closing a given memory segment is an <em>atomic</em> operation which can either succeed - and result in the underlying * is <a href="../../../java/lang/ref/package.html#reachability">unreachable</a> is not optimal.
* memory associated with the segment to be released, or <em>fail</em> with an exception. * Clients that operate under these assumptions might want to programmatically release the memory associated
* <p> * with a memory segment. This can be done, using the {@link jdk.incubator.foreign.ResourceScope} abstraction, as shown below:
* The deterministic deallocation model differs significantly from the implicit strategies adopted within other APIs, most *
* notably the {@link java.nio.ByteBuffer} API: in that case, when a native byte buffer is created (see {@link java.nio.ByteBuffer#allocateDirect(int)}), * <pre>{@code
* the underlying memory is not released until the byte buffer reference becomes <em>unreachable</em>. While implicit deallocation try (ResourceScope scope = ResourceScope.newConfinedScope()) {
* models such as this can be very convenient - clients do not have to remember to <em>close</em> a direct buffer - such models can also make it MemorySegment segment = MemorySegment.allocateNative(10 * 4, scope);
* hard for clients to ensure that the memory associated with a direct buffer has indeed been released. for (int i = 0 ; i < 10 ; i++) {
MemoryAccess.setIntAtIndex(segment, i, 42);
}
}
* }</pre>
*
* This example is almost identical to the prior one; this time we first create a so called <em>resource scope</em>,
* which is used to <em>bind</em> the life-cycle of the segment created immediately afterwards. Note the use of the
* <em>try-with-resources</em> construct: this idiom ensures that all the memory resources associated with the segment will be released
* at the end of the block, according to the semantics described in Section {@jls 14.20.3} of <cite>The Java Language Specification</cite>.
* *
* <h3><a id="safety"></a>Safety</h3> * <h3><a id="safety"></a>Safety</h3>
* *
@ -86,14 +91,9 @@ try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) {
* Section {@jls 15.10.4} of <cite>The Java Language Specification</cite>. * Section {@jls 15.10.4} of <cite>The Java Language Specification</cite>.
* <p> * <p>
* Since memory segments can be closed (see above), segments are also validated (upon access) to make sure that * Since memory segments can be closed (see above), segments are also validated (upon access) to make sure that
* the segment being accessed has not been closed prematurely. We call this guarantee <em>temporal safety</em>. Note that, * the resource scope associated with the segment being accessed has not been closed prematurely.
* in the general case, guaranteeing temporal safety can be hard, as multiple threads could attempt to access and/or close * We call this guarantee <em>temporal safety</em>. Together, spatial and temporal safety ensure that each memory access
* the same memory segment concurrently. The memory access API addresses this problem by imposing strong * operation either succeeds - and accesses a valid memory location - or fails.
* <em>thread-confinement</em> guarantees on memory segments: upon creation, a memory segment is associated with an owner thread,
* which is the only thread that can either access or close the segment.
* <p>
* Together, spatial and temporal safety ensure that each memory access operation either succeeds - and accesses a valid
* memory location - or fails.
* *
* <h2>Foreign function access</h2> * <h2>Foreign function access</h2>
* The key abstractions introduced to support foreign function access are {@link jdk.incubator.foreign.LibraryLookup} and {@link jdk.incubator.foreign.CLinker}. * The key abstractions introduced to support foreign function access are {@link jdk.incubator.foreign.LibraryLookup} and {@link jdk.incubator.foreign.CLinker}.
@ -112,8 +112,9 @@ MethodHandle strlen = CLinker.getInstance().downcallHandle(
FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER) FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER)
); );
try (var cString = CLinker.toCString("Hello")) { try (var scope = ResourceScope.newConfinedScope()) {
long len = strlen.invokeExact(cString.address()) // 5 var cString = CLinker.toCString("Hello", scope);
long len = (long)strlen.invokeExact(cString.address()); // 5
} }
* }</pre> * }</pre>
* *
@ -126,7 +127,7 @@ try (var cString = CLinker.toCString("Hello")) {
* the method handle invocation (here performed using {@link java.lang.invoke.MethodHandle#invokeExact(java.lang.Object...)}) * the method handle invocation (here performed using {@link java.lang.invoke.MethodHandle#invokeExact(java.lang.Object...)})
* into a foreign function call, according to the rules specified by the platform C ABI. The {@link jdk.incubator.foreign.CLinker} * into a foreign function call, according to the rules specified by the platform C ABI. The {@link jdk.incubator.foreign.CLinker}
* class also provides many useful methods for interacting with native code, such as converting Java strings into * class also provides many useful methods for interacting with native code, such as converting Java strings into
* native strings and viceversa (see {@link jdk.incubator.foreign.CLinker#toCString(java.lang.String)} and * native strings and viceversa (see {@link jdk.incubator.foreign.CLinker#toCString(java.lang.String, ResourceScope)} and
* {@link jdk.incubator.foreign.CLinker#toJavaString(jdk.incubator.foreign.MemorySegment)}, respectively), as * {@link jdk.incubator.foreign.CLinker#toJavaString(jdk.incubator.foreign.MemorySegment)}, respectively), as
* demonstrated in the above example. * demonstrated in the above example.
* *
@ -146,43 +147,44 @@ try (var cString = CLinker.toCString("Hello")) {
* the original segment accordingly, as follows: * the original segment accordingly, as follows:
* *
* <pre>{@code * <pre>{@code
MemorySegment segment = MemorySegment.allocateNative(100); MemorySegment segment = MemorySegment.allocateNative(100, scope);
... ...
MemoryAddress addr = ... //obtain address from native code MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(segment, addr.segmentOffset(segment)); int x = MemoryAccess.getIntAtOffset(segment, addr.segmentOffset(segment));
* }</pre> * }</pre>
* *
* Secondly, if the client does <em>not</em> have a segment which contains a given memory address, it can create one <em>unsafely</em>, * Secondly, if the client does <em>not</em> have a segment which contains a given memory address, it can create one <em>unsafely</em>,
* using the {@link jdk.incubator.foreign.MemoryAddress#asSegmentRestricted(long)} factory. This allows the client to * using the {@link jdk.incubator.foreign.MemoryAddress#asSegment(long, ResourceScope)} factory. This allows the client to
* inject extra knowledge about spatial bounds which might, for instance, be available in the documentation of the foreign function * inject extra knowledge about spatial bounds which might, for instance, be available in the documentation of the foreign function
* which produced the native address. Here is how an unsafe segment can be created from a native address: * which produced the native address. Here is how an unsafe segment can be created from a native address:
* *
* <pre>{@code * <pre>{@code
ResourceScope scope = ... // initialize a resource scope object
MemoryAddress addr = ... //obtain address from native code MemoryAddress addr = ... //obtain address from native code
MemorySegment segment = addr.asSegmentRestricted(4); // segment is 4 bytes long MemorySegment segment = addr.asSegment(4, scope); // segment is 4 bytes long
int x = MemoryAccess.getInt(segment); int x = MemoryAccess.getInt(segment);
* }</pre> * }</pre>
* *
* Alternatively, the client can fall back to use the so called <em>everything</em> segment - that is, a primordial segment * Alternatively, the client can fall back to use the so called <em>everything</em> segment - that is, a primordial segment
* which covers the entire native heap. This segment can be obtained by calling the {@link jdk.incubator.foreign.MemorySegment#ofNativeRestricted()} * which covers the entire native heap. This segment can be obtained by calling the {@link jdk.incubator.foreign.MemorySegment#globalNativeSegment()}
* method, so that dereference can happen without the need of creating any additional segment instances: * method, so that dereference can happen without the need of creating any additional segment instances:
* *
* <pre>{@code * <pre>{@code
MemoryAddress addr = ... //obtain address from native code MemoryAddress addr = ... //obtain address from native code
int x = MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr.toRawLongValue()); int x = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr.toRawLongValue());
* }</pre> * }</pre>
* *
* <h3>Upcalls</h3> * <h3>Upcalls</h3>
* The {@link jdk.incubator.foreign.CLinker} interface also allows to turn an existing method handle (which might point * The {@link jdk.incubator.foreign.CLinker} interface also allows to turn an existing method handle (which might point
* to a Java method) into a native memory segment (see {@link jdk.incubator.foreign.MemorySegment}), so that Java code * to a Java method) into a native memory address (see {@link jdk.incubator.foreign.MemoryAddress}), so that Java code
* can effectively be passed to other foreign functions. For instance, we can write a method that compares two * can effectively be passed to other foreign functions. For instance, we can write a method that compares two
* integer values, as follows: * integer values, as follows:
* *
* <pre>{@code * <pre>{@code
class IntComparator { class IntComparator {
static int intCompare(MemoryAddress addr1, MemoryAddress addr2) { static int intCompare(MemoryAddress addr1, MemoryAddress addr2) {
return MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr1.toRawLongValue()) - return MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr1.toRawLongValue()) -
MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr2.toRawLongValue()); MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr2.toRawLongValue());
} }
} }
* }</pre> * }</pre>
@ -197,39 +199,38 @@ MethodHandle intCompareHandle = MethodHandles.lookup().findStatic(IntComparator.
MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class)); MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
* }</pre> * }</pre>
* *
* Now that we have a method handle instance, we can link it into a fresh native memory segment, using the {@link jdk.incubator.foreign.CLinker} interface, as follows: * Now that we have a method handle instance, we can link it into a fresh native memory address, using the {@link jdk.incubator.foreign.CLinker} interface, as follows:
* *
* <pre>{@code * <pre>{@code
MemorySegment comparFunc = CLinker.getInstance().upcallStub( ResourceScope scope = ...
MemoryAddress comparFunc = CLinker.getInstance().upcallStub(
intCompareHandle, intCompareHandle,
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER) FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER),
scope
); );
* }</pre> * }</pre>
* *
* As before, we need to provide a {@link jdk.incubator.foreign.FunctionDescriptor} instance describing the signature * As before, we need to provide a {@link jdk.incubator.foreign.FunctionDescriptor} instance describing the signature
* of the function pointer we want to create; as before, this, coupled with the method handle type, uniquely determines the * of the function pointer we want to create; as before, this, coupled with the method handle type, uniquely determines the
* sequence of steps which will allow foreign code to call {@code intCompareHandle} according to the rules specified * sequence of steps which will allow foreign code to call {@code intCompareHandle} according to the rules specified
* by the platform C ABI. * by the platform C ABI. The lifecycle of the memory address returned by
* {@link jdk.incubator.foreign.CLinker#upcallStub(java.lang.invoke.MethodHandle, jdk.incubator.foreign.FunctionDescriptor, jdk.incubator.foreign.ResourceScope)}
* is tied to the {@linkplain jdk.incubator.foreign.ResourceScope resource scope} parameter passed to that method.
* *
* <a id="restricted"></a>
* <h2>Restricted methods</h2> * <h2>Restricted methods</h2>
* Some methods in this package are considered <em>restricted</em>. Restricted methods are typically used to bind native * Some methods in this package are considered <em>restricted</em>. Restricted methods are typically used to bind native
* foreign data and/or functions to first-class Java API elements which can then be used directly by client. For instance * foreign data and/or functions to first-class Java API elements which can then be used directly by clients. For instance
* the restricted method {@link jdk.incubator.foreign.MemoryAddress#asSegmentRestricted(long)} can be used to create * the restricted method {@link jdk.incubator.foreign.MemoryAddress#asSegment(long, ResourceScope)} can be used to create
* a fresh segment with given spatial bounds out of a native address. * a fresh segment with given spatial bounds out of a native address.
* <p> * <p>
* Binding foreign data and/or functions is generally unsafe and, if done incorrectly, can result in VM crashes, or memory corruption when the bound Java API element is accessed. * Binding foreign data and/or functions is generally unsafe and, if done incorrectly, can result in VM crashes, or memory corruption when the bound Java API element is accessed.
* For instance, in the case of {@link jdk.incubator.foreign.MemoryAddress#asSegmentRestricted(long)}, if the provided * For instance, in the case of {@link jdk.incubator.foreign.MemoryAddress#asSegment(long, ResourceScope)}, if the provided
* spatial bounds are incorrect, a client of the segment returned by that method might crash the VM, or corrupt * spatial bounds are incorrect, a client of the segment returned by that method might crash the VM, or corrupt
* memory when attempting to dereference said segment. For these reasons, it is crucial for code that calls a restricted method * memory when attempting to dereference said segment. For these reasons, it is crucial for code that calls a restricted method
* to never pass arguments that might cause incorrect binding of foreign data and/or functions to a Java API. * to never pass arguments that might cause incorrect binding of foreign data and/or functions to a Java API.
* <p> * <p>
* Access to restricted methods is <em>disabled</em> by default; to enable restricted methods, the JDK property * Access to restricted methods is <em>disabled</em> by default; to enable restricted methods, the command line option
* {@code foreign.restricted} must be set to a value other than {@code deny}. The possible values for this property are: * {@code --enable-native-access} must mention the name of the caller's module.
* <ul>
* <li>{@code deny}: issues a runtime exception on each restricted call. This is the default value;</li>
* <li>{@code permit}: allows restricted calls;</li>
* <li>{@code warn}: like permit, but also prints a one-line warning on each restricted call;</li>
* <li>{@code debug}: like permit, but also dumps the stack corresponding to any given restricted call.</li>
* </ul>
*/ */
package jdk.incubator.foreign; package jdk.incubator.foreign;

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2021, 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;
import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SegmentAllocator;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
public abstract non-sealed class AbstractCLinker implements CLinker {
public final MethodHandle downcallHandle(Addressable symbol, MethodType type, FunctionDescriptor function) {
Objects.requireNonNull(symbol);
return MethodHandles.insertArguments(downcallHandle(type, function), 0, symbol);
}
public final MethodHandle downcallHandle(Addressable symbol, SegmentAllocator allocator, MethodType type, FunctionDescriptor function) {
Objects.requireNonNull(symbol);
Objects.requireNonNull(allocator);
MethodHandle downcall = MethodHandles.insertArguments(downcallHandle(type, function), 0, symbol);
if (type.returnType().equals(MemorySegment.class)) {
downcall = MethodHandles.insertArguments(downcall, 0, allocator);
}
return downcall;
}
}

View file

@ -35,43 +35,42 @@ import jdk.internal.util.ArraysSupport;
import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.ForceInline;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
import java.io.FileDescriptor;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/** /**
* This abstract class provides an immutable implementation for the {@code MemorySegment} interface. This class contains information * This abstract class provides an immutable implementation for the {@code MemorySegment} interface. This class contains information
* about the segment's spatial and temporal bounds; each memory segment implementation is associated with an owner thread which is set at creation time. * about the segment's spatial and temporal bounds; each memory segment implementation is associated with an owner thread which is set at creation time.
* Access to certain sensitive operations on the memory segment will fail with {@code IllegalStateException} if the * Access to certain sensitive operations on the memory segment will fail with {@code IllegalStateException} if the
* segment is either in an invalid state (e.g. it has already been closed) or if access occurs from a thread other * segment is either in an invalid state (e.g. it has already been closed) or if access occurs from a thread other
* than the owner thread. See {@link MemoryScope} for more details on management of temporal bounds. Subclasses * than the owner thread. See {@link ResourceScopeImpl} for more details on management of temporal bounds. Subclasses
* are defined for each memory segment kind, see {@link NativeMemorySegmentImpl}, {@link HeapMemorySegmentImpl} and * are defined for each memory segment kind, see {@link NativeMemorySegmentImpl}, {@link HeapMemorySegmentImpl} and
* {@link MappedMemorySegmentImpl}. * {@link MappedMemorySegmentImpl}.
*/ */
public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy implements MemorySegment { public abstract non-sealed class AbstractMemorySegmentImpl extends MemorySegmentProxy implements MemorySegment {
private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
private static final boolean enableSmallSegments = private static final boolean enableSmallSegments =
Boolean.parseBoolean(GetPropertyAction.privilegedGetProperty("jdk.incubator.foreign.SmallSegments", "true")); Boolean.parseBoolean(GetPropertyAction.privilegedGetProperty("jdk.incubator.foreign.SmallSegments", "true"));
final static int FIRST_RESERVED_FLAG = 1 << 16; // upper 16 bits are reserved static final int READ_ONLY = 1;
final static int SMALL = FIRST_RESERVED_FLAG; static final int SMALL = READ_ONLY << 1;
final static long NONCE = new Random().nextLong(); static final long NONCE = new Random().nextLong();
final static JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess(); static final JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();
final long length; final long length;
final int mask; final int mask;
final MemoryScope scope; final ResourceScopeImpl scope;
@ForceInline @ForceInline
AbstractMemorySegmentImpl(long length, int mask, MemoryScope scope) { AbstractMemorySegmentImpl(long length, int mask, ResourceScopeImpl scope) {
this.length = length; this.length = length;
this.mask = mask; this.mask = mask;
this.scope = scope; this.scope = scope;
@ -81,14 +80,23 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
abstract Object base(); abstract Object base();
abstract AbstractMemorySegmentImpl dup(long offset, long size, int mask, MemoryScope scope); abstract AbstractMemorySegmentImpl dup(long offset, long size, int mask, ResourceScopeImpl scope);
abstract ByteBuffer makeByteBuffer(); abstract ByteBuffer makeByteBuffer();
static int defaultAccessModes(long size) { static int defaultAccessModes(long size) {
return (enableSmallSegments && size < Integer.MAX_VALUE) ? return (enableSmallSegments && size < Integer.MAX_VALUE) ?
ALL_ACCESS | SMALL : SMALL : 0;
ALL_ACCESS; }
@Override
public AbstractMemorySegmentImpl asReadOnly() {
return dup(0, length, mask | READ_ONLY, scope);
}
@Override
public boolean isReadOnly() {
return isSet(READ_ONLY);
} }
@Override @Override
@ -108,14 +116,21 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
} }
@Override @Override
public Spliterator<MemorySegment> spliterator(SequenceLayout sequenceLayout) { public Spliterator<MemorySegment> spliterator(MemoryLayout elementLayout) {
Objects.requireNonNull(sequenceLayout); Objects.requireNonNull(elementLayout);
checkValidState(); if (elementLayout.byteSize() == 0) {
if (sequenceLayout.byteSize() != byteSize()) { throw new IllegalArgumentException("Element layout size cannot be zero");
throw new IllegalArgumentException();
} }
return new SegmentSplitter(sequenceLayout.elementLayout().byteSize(), sequenceLayout.elementCount().getAsLong(), if (byteSize() % elementLayout.byteSize() != 0) {
withAccessModes(accessModes() & ~CLOSE)); throw new IllegalArgumentException("Segment size is no a multiple of layout size");
}
return new SegmentSplitter(elementLayout.byteSize(), byteSize() / elementLayout.byteSize(),
this);
}
@Override
public Stream<MemorySegment> elements(MemoryLayout elementLayout) {
return StreamSupport.stream(spliterator(elementLayout), false);
} }
@Override @Override
@ -185,7 +200,7 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
/** /**
* Mismatch over long lengths. * Mismatch over long lengths.
*/ */
private static long vectorizedMismatchLargeForBytes(MemoryScope aScope, MemoryScope bScope, private static long vectorizedMismatchLargeForBytes(ResourceScopeImpl aScope, ResourceScopeImpl bScope,
Object a, long aOffset, Object a, long aOffset,
Object b, long bOffset, Object b, long bOffset,
long length) { long length) {
@ -217,132 +232,63 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
@Override @Override
@ForceInline @ForceInline
public final MemoryAddress address() { public final MemoryAddress address() {
checkValidState(); return new MemoryAddressImpl(this, 0L);
return new MemoryAddressImpl(base(), min());
} }
@Override @Override
public final ByteBuffer asByteBuffer() { public final ByteBuffer asByteBuffer() {
if (!isSet(READ)) {
throw unsupportedAccessMode(READ);
}
checkArraySize("ByteBuffer", 1); checkArraySize("ByteBuffer", 1);
ByteBuffer _bb = makeByteBuffer(); ByteBuffer _bb = makeByteBuffer();
if (!isSet(WRITE)) { if (isSet(READ_ONLY)) {
//scope is IMMUTABLE - obtain a RO byte buffer //scope is IMMUTABLE - obtain a RO byte buffer
_bb = _bb.asReadOnlyBuffer(); _bb = _bb.asReadOnlyBuffer();
} }
return _bb; return _bb;
} }
@Override
public final int accessModes() {
return mask & ALL_ACCESS;
}
@Override @Override
public final long byteSize() { public final long byteSize() {
return length; return length;
} }
@Override
public final boolean isAlive() { public final boolean isAlive() {
return scope.isAlive(); return scope.isAlive();
} }
@Override
public Thread ownerThread() { public Thread ownerThread() {
return scope.ownerThread(); return scope.ownerThread();
} }
@Override
public AbstractMemorySegmentImpl withAccessModes(int accessModes) {
checkAccessModes(accessModes);
if ((~accessModes() & accessModes) != 0) {
throw new IllegalArgumentException("Cannot acquire more access modes");
}
return dup(0, length, (mask & ~ALL_ACCESS) | accessModes, scope);
}
@Override
public boolean hasAccessModes(int accessModes) {
checkAccessModes(accessModes);
return (accessModes() & accessModes) == accessModes;
}
private void checkAccessModes(int accessModes) {
if ((accessModes & ~ALL_ACCESS) != 0) {
throw new IllegalArgumentException("Invalid access modes");
}
}
public MemorySegment handoff(Thread thread) {
Objects.requireNonNull(thread);
checkValidState();
if (!isSet(HANDOFF)) {
throw unsupportedAccessMode(HANDOFF);
}
try {
return dup(0L, length, mask, scope.confineTo(thread));
} finally {
//flush read/writes to segment memory before returning the new segment
VarHandle.fullFence();
}
}
@Override
public MemorySegment share() {
checkValidState();
if (!isSet(SHARE)) {
throw unsupportedAccessMode(SHARE);
}
try {
return dup(0L, length, mask, scope.share());
} finally {
//flush read/writes to segment memory before returning the new segment
VarHandle.fullFence();
}
}
@Override
public MemorySegment handoff(NativeScope scope) {
Objects.requireNonNull(scope);
checkValidState();
if (!isSet(HANDOFF)) {
throw unsupportedAccessMode(HANDOFF);
}
if (!isSet(CLOSE)) {
throw unsupportedAccessMode(CLOSE);
}
MemorySegment dup = handoff(scope.ownerThread());
((AbstractNativeScope)scope).register(dup);
return dup.withAccessModes(accessModes() & (READ | WRITE));
}
@Override
public MemorySegment registerCleaner(Cleaner cleaner) {
Objects.requireNonNull(cleaner);
checkValidState();
if (!isSet(CLOSE)) {
throw unsupportedAccessMode(CLOSE);
}
return dup(0L, length, mask, scope.cleanable(cleaner));
}
@Override
public final void close() {
checkValidState();
if (!isSet(CLOSE)) {
throw unsupportedAccessMode(CLOSE);
}
scope.close();
}
@Override @Override
public boolean isMapped() { public boolean isMapped() {
return false; return false;
} }
@Override
public boolean isNative() {
return false;
}
@Override
public void load() {
throw new UnsupportedOperationException("Not a mapped segment");
}
@Override
public void unload() {
throw new UnsupportedOperationException("Not a mapped segment");
}
@Override
public boolean isLoaded() {
throw new UnsupportedOperationException("Not a mapped segment");
}
@Override
public void force() {
throw new UnsupportedOperationException("Not a mapped segment");
}
@Override @Override
public final byte[] toByteArray() { public final byte[] toByteArray() {
return toArray(byte[].class, 1, byte[]::new, MemorySegment::ofArray); return toArray(byte[].class, 1, byte[]::new, MemorySegment::ofArray);
@ -393,20 +339,13 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
@Override @Override
public void checkAccess(long offset, long length, boolean readOnly) { public void checkAccess(long offset, long length, boolean readOnly) {
if (!readOnly && !isSet(WRITE)) { if (!readOnly && isSet(READ_ONLY)) {
throw unsupportedAccessMode(WRITE); throw new UnsupportedOperationException("Attempt to write a read-only segment");
} else if (readOnly && !isSet(READ)) {
throw unsupportedAccessMode(READ);
} }
checkBounds(offset, length); checkBounds(offset, length);
} }
private void checkAccessAndScope(long offset, long length, boolean readOnly) { void checkValidState() {
checkValidState();
checkAccess(offset, length, readOnly);
}
private void checkValidState() {
try { try {
scope.checkValidState(); scope.checkValidState();
} catch (ScopedMemoryAccess.Scope.ScopedAccessError ex) { } catch (ScopedMemoryAccess.Scope.ScopedAccessError ex) {
@ -432,17 +371,19 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
private int checkArraySize(String typeName, int elemSize) { private int checkArraySize(String typeName, int elemSize) {
if (length % elemSize != 0) { if (length % elemSize != 0) {
throw new UnsupportedOperationException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, length)); throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, length));
} }
long arraySize = length / elemSize; long arraySize = length / elemSize;
if (arraySize > (Integer.MAX_VALUE - 8)) { //conservative check if (arraySize > (Integer.MAX_VALUE - 8)) { //conservative check
throw new UnsupportedOperationException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, length)); throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, length));
} }
return (int)arraySize; return (int)arraySize;
} }
private void checkBounds(long offset, long length) { private void checkBounds(long offset, long length) {
if (isSmall()) { if (isSmall() &&
offset < Integer.MAX_VALUE && length < Integer.MAX_VALUE &&
offset > Integer.MIN_VALUE && length > Integer.MIN_VALUE) {
checkBoundsSmall((int)offset, (int)length); checkBoundsSmall((int)offset, (int)length);
} else { } else {
if (length < 0 || if (length < 0 ||
@ -454,7 +395,7 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
} }
@Override @Override
public MemoryScope scope() { public ResourceScopeImpl scope() {
return scope; return scope;
} }
@ -466,31 +407,6 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
} }
} }
UnsupportedOperationException unsupportedAccessMode(int expected) {
return new UnsupportedOperationException((String.format("Required access mode %s ; current access modes: %s",
modeStrings(expected).get(0), modeStrings(mask))));
}
private List<String> modeStrings(int mode) {
List<String> modes = new ArrayList<>();
if ((mode & READ) != 0) {
modes.add("READ");
}
if ((mode & WRITE) != 0) {
modes.add("WRITE");
}
if ((mode & CLOSE) != 0) {
modes.add("CLOSE");
}
if ((mode & SHARE) != 0) {
modes.add("SHARE");
}
if ((mode & HANDOFF) != 0) {
modes.add("HANDOFF");
}
return modes;
}
private IndexOutOfBoundsException outOfBoundException(long offset, long length) { private IndexOutOfBoundsException outOfBoundException(long offset, long length) {
return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; new offset = %d; new length = %d", return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; new offset = %d; new length = %d",
this, offset, length)); this, offset, length));
@ -602,20 +518,20 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
int size = limit - pos; int size = limit - pos;
AbstractMemorySegmentImpl bufferSegment = (AbstractMemorySegmentImpl)nioAccess.bufferSegment(bb); AbstractMemorySegmentImpl bufferSegment = (AbstractMemorySegmentImpl)nioAccess.bufferSegment(bb);
final MemoryScope bufferScope; final ResourceScopeImpl bufferScope;
int modes; int modes;
if (bufferSegment != null) { if (bufferSegment != null) {
bufferScope = bufferSegment.scope; bufferScope = bufferSegment.scope;
modes = bufferSegment.mask; modes = bufferSegment.mask;
} else { } else {
bufferScope = MemoryScope.createConfined(bb, MemoryScope.DUMMY_CLEANUP_ACTION, null); bufferScope = ResourceScopeImpl.GLOBAL;
modes = defaultAccessModes(size); modes = defaultAccessModes(size);
} }
if (bb.isReadOnly()) { if (bb.isReadOnly()) {
modes &= ~WRITE; modes |= READ_ONLY;
} }
if (base != null) { if (base != null) {
return new HeapMemorySegmentImpl.OfByte(bbAddress + pos, (byte[])base, size, modes, bufferScope); return new HeapMemorySegmentImpl.OfByte(bbAddress + pos, (byte[])base, size, modes);
} else if (unmapper == null) { } else if (unmapper == null) {
return new NativeMemorySegmentImpl(bbAddress + pos, size, modes, bufferScope); return new NativeMemorySegmentImpl(bbAddress + pos, size, modes, bufferScope);
} else { } else {

View file

@ -1,172 +0,0 @@
/*
* 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;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.NativeScope;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalLong;
public abstract class AbstractNativeScope implements NativeScope {
private final List<MemorySegment> segments = new ArrayList<>();
private final Thread ownerThread;
private static final int SCOPE_MASK = MemorySegment.READ | MemorySegment.WRITE; // no terminal operations allowed
AbstractNativeScope() {
this.ownerThread = Thread.currentThread();
}
@Override
public Thread ownerThread() {
return ownerThread;
}
@Override
public void close() {
segments.forEach(MemorySegment::close);
}
void checkOwnerThread() {
if (Thread.currentThread() != ownerThread()) {
throw new IllegalStateException("Attempt to access scope from different thread");
}
}
MemorySegment newSegment(long size, long align) {
MemorySegment segment = MemorySegment.allocateNative(size, align);
segments.add(segment);
return segment;
}
MemorySegment newSegment(long size) {
return newSegment(size, size);
}
public void register(MemorySegment segment) {
segments.add(segment);
}
public static class UnboundedNativeScope extends AbstractNativeScope {
private static final long BLOCK_SIZE = 4 * 1024;
private static final long MAX_ALLOC_SIZE = BLOCK_SIZE / 2;
private MemorySegment segment;
private long sp = 0L;
private long size = 0L;
@Override
public OptionalLong byteSize() {
return OptionalLong.empty();
}
@Override
public long allocatedBytes() {
return size;
}
public UnboundedNativeScope() {
super();
this.segment = newSegment(BLOCK_SIZE);
}
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
checkOwnerThread();
if (Utils.alignUp(bytesSize, bytesAlignment) > MAX_ALLOC_SIZE) {
MemorySegment segment = newSegment(bytesSize, bytesAlignment);
return segment.withAccessModes(SCOPE_MASK);
}
// try to slice from current segment first...
MemorySegment slice = trySlice(bytesSize, bytesAlignment);
if (slice == null) {
// ... if that fails, allocate a new segment and slice from there
sp = 0L;
segment = newSegment(BLOCK_SIZE, 1L);
slice = trySlice(bytesSize, bytesAlignment);
if (slice == null) {
// this should not be possible - allocations that do not fit in BLOCK_SIZE should get their own
// standalone segment (see above).
throw new AssertionError("Cannot get here!");
}
}
return slice;
}
private MemorySegment trySlice(long bytesSize, long bytesAlignment) {
long min = segment.address().toRawLongValue();
long start = Utils.alignUp(min + sp, bytesAlignment) - min;
if (segment.byteSize() - start < bytesSize) {
return null;
} else {
MemorySegment slice = segment.asSlice(start, bytesSize)
.withAccessModes(SCOPE_MASK);
sp = start + bytesSize;
size += Utils.alignUp(bytesSize, bytesAlignment);
return slice;
}
}
}
public static class BoundedNativeScope extends AbstractNativeScope {
private final MemorySegment segment;
private long sp = 0L;
@Override
public OptionalLong byteSize() {
return OptionalLong.of(segment.byteSize());
}
@Override
public long allocatedBytes() {
return sp;
}
public BoundedNativeScope(long size) {
super();
this.segment = newSegment(size, 1);
}
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
checkOwnerThread();
long min = segment.address().toRawLongValue();
long start = Utils.alignUp(min + sp, bytesAlignment) - min;
try {
MemorySegment slice = segment.asSlice(start, bytesSize)
.withAccessModes(SCOPE_MASK);
sp = start + bytesSize;
return slice;
} catch (IndexOutOfBoundsException ex) {
throw new OutOfMemoryError("Not enough space left to allocate");
}
}
}
}

View file

@ -0,0 +1,124 @@
package jdk.internal.foreign;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.incubator.foreign.ResourceScope;
public abstract class ArenaAllocator implements SegmentAllocator {
protected MemorySegment segment;
protected long sp = 0L;
ArenaAllocator(MemorySegment segment) {
this.segment = segment;
}
MemorySegment trySlice(long bytesSize, long bytesAlignment) {
long min = segment.address().toRawLongValue();
long start = Utils.alignUp(min + sp, bytesAlignment) - min;
if (segment.byteSize() - start < bytesSize) {
return null;
} else {
MemorySegment slice = segment.asSlice(start, bytesSize);
sp = start + bytesSize;
return slice;
}
}
void checkConfinementIfNeeded() {
Thread ownerThread = scope().ownerThread();
if (ownerThread != null && ownerThread != Thread.currentThread()) {
throw new IllegalStateException("Attempt to allocate outside confinement thread");
}
}
ResourceScope scope() {
return segment.scope();
}
public static class UnboundedArenaAllocator extends ArenaAllocator {
private static final long DEFAULT_BLOCK_SIZE = 4 * 1024;
public UnboundedArenaAllocator(ResourceScope scope) {
super(MemorySegment.allocateNative(DEFAULT_BLOCK_SIZE, 1, scope));
}
private MemorySegment newSegment(long size, long align) {
return MemorySegment.allocateNative(size, align, segment.scope());
}
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
checkConfinementIfNeeded();
// try to slice from current segment first...
MemorySegment slice = trySlice(bytesSize, bytesAlignment);
if (slice != null) {
return slice;
} else {
long maxPossibleAllocationSize = bytesSize + bytesAlignment - 1;
if (maxPossibleAllocationSize > DEFAULT_BLOCK_SIZE) {
// too big
return newSegment(bytesSize, bytesAlignment);
} else {
// allocate a new segment and slice from there
sp = 0L;
segment = newSegment(DEFAULT_BLOCK_SIZE, 1L);
return trySlice(bytesSize, bytesAlignment);
}
}
}
}
public static class BoundedArenaAllocator extends ArenaAllocator {
public BoundedArenaAllocator(ResourceScope scope, long size) {
super(MemorySegment.allocateNative(size, 1, scope));
}
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
checkConfinementIfNeeded();
// try to slice from current segment first...
MemorySegment slice = trySlice(bytesSize, bytesAlignment);
if (slice != null) {
return slice;
} else {
throw new OutOfMemoryError("Not enough space left to allocate");
}
}
}
public static class BoundedSharedArenaAllocator extends BoundedArenaAllocator {
public BoundedSharedArenaAllocator(ResourceScope scope, long size) {
super(scope, size);
}
@Override
public synchronized MemorySegment allocate(long bytesSize, long bytesAlignment) {
return super.allocate(bytesSize, bytesAlignment);
}
}
public static class UnboundedSharedArenaAllocator implements SegmentAllocator {
final ResourceScope scope;
final ThreadLocal<ArenaAllocator> allocators = new ThreadLocal<>() {
@Override
protected ArenaAllocator initialValue() {
return new UnboundedArenaAllocator(scope);
}
};
public UnboundedSharedArenaAllocator(ResourceScope scope) {
this.scope = scope;
}
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
return allocators.get().allocate(bytesSize, bytesAlignment);
}
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2021, 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;
import jdk.incubator.foreign.ResourceScope;
import jdk.internal.vm.annotation.ForceInline;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
/**
* A confined scope, which features an owner thread. The liveness check features an additional
* confinement check - that is, calling any operation on this scope from a thread other than the
* owner thread will result in an exception. Because of this restriction, checking the liveness bit
* can be performed in plain mode.
*/
final class ConfinedScope extends ResourceScopeImpl {
private boolean closed; // = false
private int lockCount = 0;
private final Thread owner;
public ConfinedScope(Thread owner, Cleaner cleaner) {
super(cleaner, new ConfinedResourceList());
this.owner = owner;
}
@ForceInline
public final void checkValidState() {
if (owner != Thread.currentThread()) {
throw new IllegalStateException("Attempted access outside owning thread");
}
if (closed) {
throw new IllegalStateException("Already closed");
}
}
@Override
public boolean isAlive() {
return !closed;
}
@Override
public HandleImpl acquire() {
checkValidState();
lockCount++;
return new ConfinedHandle();
}
void justClose() {
this.checkValidState();
if (lockCount == 0) {
closed = true;
} else {
throw new IllegalStateException("Scope is acquired by " + lockCount + " locks");
}
}
@Override
public Thread ownerThread() {
return owner;
}
/**
* A confined resource list; no races are possible here.
*/
static final class ConfinedResourceList extends ResourceList {
@Override
void add(ResourceCleanup cleanup) {
if (fst != ResourceCleanup.CLOSED_LIST) {
cleanup.next = fst;
fst = cleanup;
} else {
throw new IllegalStateException("Already closed!");
}
}
@Override
void cleanup() {
if (fst != ResourceCleanup.CLOSED_LIST) {
ResourceCleanup prev = fst;
fst = ResourceCleanup.CLOSED_LIST;
cleanup(prev);
} else {
throw new IllegalStateException("Attempt to cleanup an already closed resource list");
}
}
}
/**
* A confined resource scope handle; no races are possible here.
*/
final class ConfinedHandle implements HandleImpl {
boolean released = false;
@Override
public ResourceScopeImpl scope() {
return ConfinedScope.this;
}
@Override
public void release() {
checkValidState(); // thread check
if (!released) {
released = true;
lockCount--;
}
}
}
}

View file

@ -34,7 +34,6 @@ import jdk.internal.vm.annotation.ForceInline;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
/** /**
* Implementation for heap memory segments. An heap memory segment is composed by an offset and * Implementation for heap memory segments. An heap memory segment is composed by an offset and
@ -52,8 +51,8 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
final H base; final H base;
@ForceInline @ForceInline
HeapMemorySegmentImpl(long offset, H base, long length, int mask, MemoryScope scope) { HeapMemorySegmentImpl(long offset, H base, long length, int mask) {
super(length, mask, scope); super(length, mask, ResourceScopeImpl.GLOBAL);
this.offset = offset; this.offset = offset;
this.base = base; this.base = base;
} }
@ -67,7 +66,7 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
} }
@Override @Override
abstract HeapMemorySegmentImpl<H> dup(long offset, long size, int mask, MemoryScope scope); abstract HeapMemorySegmentImpl<H> dup(long offset, long size, int mask, ResourceScopeImpl scope);
@Override @Override
ByteBuffer makeByteBuffer() { ByteBuffer makeByteBuffer() {
@ -75,20 +74,20 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
throw new UnsupportedOperationException("Not an address to an heap-allocated byte array"); throw new UnsupportedOperationException("Not an address to an heap-allocated byte array");
} }
JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess(); JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();
return nioAccess.newHeapByteBuffer((byte[]) base(), (int)min() - BYTE_ARR_BASE, (int) byteSize(), this); return nioAccess.newHeapByteBuffer((byte[]) base(), (int)min() - BYTE_ARR_BASE, (int) byteSize(), null);
} }
// factories // factories
public static class OfByte extends HeapMemorySegmentImpl<byte[]> { public static class OfByte extends HeapMemorySegmentImpl<byte[]> {
OfByte(long offset, byte[] base, long length, int mask, MemoryScope scope) { OfByte(long offset, byte[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfByte dup(long offset, long size, int mask, MemoryScope scope) { OfByte dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfByte(this.offset + offset, base, size, mask, scope); return new OfByte(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -99,20 +98,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(byte[] arr) { public static MemorySegment fromArray(byte[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_BYTE_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_BYTE_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfChar extends HeapMemorySegmentImpl<char[]> { public static class OfChar extends HeapMemorySegmentImpl<char[]> {
OfChar(long offset, char[] base, long length, int mask, MemoryScope scope) { OfChar(long offset, char[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfChar dup(long offset, long size, int mask, MemoryScope scope) { OfChar dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfChar(this.offset + offset, base, size, mask, scope); return new OfChar(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -123,20 +121,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(char[] arr) { public static MemorySegment fromArray(char[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_CHAR_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_CHAR_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfShort extends HeapMemorySegmentImpl<short[]> { public static class OfShort extends HeapMemorySegmentImpl<short[]> {
OfShort(long offset, short[] base, long length, int mask, MemoryScope scope) { OfShort(long offset, short[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfShort dup(long offset, long size, int mask, MemoryScope scope) { OfShort dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfShort(this.offset + offset, base, size, mask, scope); return new OfShort(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -147,20 +144,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(short[] arr) { public static MemorySegment fromArray(short[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_SHORT_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_SHORT_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfInt extends HeapMemorySegmentImpl<int[]> { public static class OfInt extends HeapMemorySegmentImpl<int[]> {
OfInt(long offset, int[] base, long length, int mask, MemoryScope scope) { OfInt(long offset, int[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfInt dup(long offset, long size, int mask, MemoryScope scope) { OfInt dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfInt(this.offset + offset, base, size, mask, scope); return new OfInt(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -171,20 +167,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(int[] arr) { public static MemorySegment fromArray(int[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_INT_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_INT_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfLong extends HeapMemorySegmentImpl<long[]> { public static class OfLong extends HeapMemorySegmentImpl<long[]> {
OfLong(long offset, long[] base, long length, int mask, MemoryScope scope) { OfLong(long offset, long[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfLong dup(long offset, long size, int mask, MemoryScope scope) { OfLong dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfLong(this.offset + offset, base, size, mask, scope); return new OfLong(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -195,20 +190,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(long[] arr) { public static MemorySegment fromArray(long[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_LONG_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_LONG_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfFloat extends HeapMemorySegmentImpl<float[]> { public static class OfFloat extends HeapMemorySegmentImpl<float[]> {
OfFloat(long offset, float[] base, long length, int mask, MemoryScope scope) { OfFloat(long offset, float[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfFloat dup(long offset, long size, int mask, MemoryScope scope) { OfFloat dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfFloat(this.offset + offset, base, size, mask, scope); return new OfFloat(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -219,20 +213,19 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(float[] arr) { public static MemorySegment fromArray(float[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_FLOAT_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_FLOAT_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
public static class OfDouble extends HeapMemorySegmentImpl<double[]> { public static class OfDouble extends HeapMemorySegmentImpl<double[]> {
OfDouble(long offset, double[] base, long length, int mask, MemoryScope scope) { OfDouble(long offset, double[] base, long length, int mask) {
super(offset, base, length, mask, scope); super(offset, base, length, mask);
} }
@Override @Override
OfDouble dup(long offset, long size, int mask, MemoryScope scope) { OfDouble dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new OfDouble(this.offset + offset, base, size, mask, scope); return new OfDouble(this.offset + offset, base, size, mask);
} }
@Override @Override
@ -243,8 +236,8 @@ public abstract class HeapMemorySegmentImpl<H> extends AbstractMemorySegmentImpl
public static MemorySegment fromArray(double[] arr) { public static MemorySegment fromArray(double[] arr) {
Objects.requireNonNull(arr); Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_DOUBLE_INDEX_SCALE; long byteSize = (long)arr.length * Unsafe.ARRAY_DOUBLE_INDEX_SCALE;
MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null); return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
} }
} }
} }

View file

@ -35,7 +35,6 @@ import jdk.internal.access.foreign.MemorySegmentProxy;
import jdk.incubator.foreign.GroupLayout; import jdk.incubator.foreign.GroupLayout;
import jdk.incubator.foreign.SequenceLayout; import jdk.incubator.foreign.SequenceLayout;
import jdk.incubator.foreign.ValueLayout; import jdk.incubator.foreign.ValueLayout;
import sun.invoke.util.Wrapper;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
@ -62,6 +61,7 @@ public class LayoutPath {
private static final MethodHandle ADD_STRIDE; private static final MethodHandle ADD_STRIDE;
private static final MethodHandle MH_ADD_SCALED_OFFSET; private static final MethodHandle MH_ADD_SCALED_OFFSET;
private static final MethodHandle MH_SLICE;
private static final int UNSPECIFIED_ELEM_INDEX = -1; private static final int UNSPECIFIED_ELEM_INDEX = -1;
@ -72,6 +72,8 @@ public class LayoutPath {
MethodType.methodType(long.class, MemorySegment.class, long.class, long.class, long.class)); MethodType.methodType(long.class, MemorySegment.class, long.class, long.class, long.class));
MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset", MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset",
MethodType.methodType(long.class, long.class, long.class, long.class)); MethodType.methodType(long.class, long.class, long.class, long.class));
MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice",
MethodType.methodType(MemorySegment.class, long.class, long.class));
} catch (Throwable ex) { } catch (Throwable ex) {
throw new ExceptionInInitializerError(ex); throw new ExceptionInInitializerError(ex);
} }
@ -200,6 +202,22 @@ public class LayoutPath {
return mh; return mh;
} }
public MethodHandle sliceHandle() {
if (strides.length == 0) {
// trigger checks eagerly
Utils.bitsToBytesOrThrow(offset, Utils.bitsToBytesThrowOffset);
}
MethodHandle offsetHandle = offsetHandle(); // bit offset
offsetHandle = MethodHandles.filterReturnValue(offsetHandle, Utils.MH_bitsToBytesOrThrowForOffset); // byte offset
MethodHandle sliceHandle = MH_SLICE; // (MS, long, long) -> MS
sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS
sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle); // (MS, ...) -> MS
return sliceHandle;
}
public MemoryLayout layout() { public MemoryLayout layout() {
return layout; return layout;
} }
@ -211,9 +229,9 @@ public class LayoutPath {
} else if (enclosing.layout instanceof SequenceLayout) { } else if (enclosing.layout instanceof SequenceLayout) {
SequenceLayout seq = (SequenceLayout)enclosing.layout; SequenceLayout seq = (SequenceLayout)enclosing.layout;
if (seq.elementCount().isPresent()) { if (seq.elementCount().isPresent()) {
return enclosing.map(l -> dup(l, MemoryLayout.ofSequence(seq.elementCount().getAsLong(), newLayout))); return enclosing.map(l -> dup(l, MemoryLayout.sequenceLayout(seq.elementCount().getAsLong(), newLayout)));
} else { } else {
return enclosing.map(l -> dup(l, MemoryLayout.ofSequence(newLayout))); return enclosing.map(l -> dup(l, MemoryLayout.sequenceLayout(newLayout)));
} }
} else if (enclosing.layout instanceof GroupLayout) { } else if (enclosing.layout instanceof GroupLayout) {
GroupLayout g = (GroupLayout)enclosing.layout; GroupLayout g = (GroupLayout)enclosing.layout;
@ -221,9 +239,9 @@ public class LayoutPath {
//if we selected a layout in a group we must have a valid index //if we selected a layout in a group we must have a valid index
newElements.set((int)elementIndex, newLayout); newElements.set((int)elementIndex, newLayout);
if (g.isUnion()) { if (g.isUnion()) {
return enclosing.map(l -> dup(l, MemoryLayout.ofUnion(newElements.toArray(new MemoryLayout[0])))); return enclosing.map(l -> dup(l, MemoryLayout.unionLayout(newElements.toArray(new MemoryLayout[0]))));
} else { } else {
return enclosing.map(l -> dup(l, MemoryLayout.ofStruct(newElements.toArray(new MemoryLayout[0])))); return enclosing.map(l -> dup(l, MemoryLayout.structLayout(newElements.toArray(new MemoryLayout[0]))));
} }
} else { } else {
return newLayout; return newLayout;
@ -299,7 +317,7 @@ public class LayoutPath {
* This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation * This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation
* is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class. * is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class.
*/ */
public static class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> { public static final class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {
public enum PathKind { public enum PathKind {
SEQUENCE_ELEMENT("unbound sequence element"), SEQUENCE_ELEMENT("unbound sequence element"),

View file

@ -29,27 +29,27 @@ import jdk.incubator.foreign.MemoryAddress;
import java.io.File; import java.io.File;
import jdk.incubator.foreign.LibraryLookup; import jdk.incubator.foreign.LibraryLookup;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.internal.loader.NativeLibraries; import jdk.internal.loader.NativeLibraries;
import jdk.internal.loader.NativeLibrary; import jdk.internal.loader.NativeLibrary;
import jdk.internal.ref.CleanerFactory;
import java.nio.file.Files; import java.lang.ref.Reference;
import java.nio.file.Path; import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier; import java.util.function.Supplier;
public final class LibrariesHelper { public final class LibrariesHelper {
private LibrariesHelper() {} private LibrariesHelper() {}
private final static NativeLibraries nativeLibraries = private static final NativeLibraries nativeLibraries =
NativeLibraries.rawNativeLibraries(LibrariesHelper.class, true); NativeLibraries.rawNativeLibraries(LibrariesHelper.class, true);
private final static Map<NativeLibrary, AtomicInteger> loadedLibraries = new IdentityHashMap<>(); private static final Map<NativeLibrary, WeakReference<ResourceScope>> loadedLibraries = new ConcurrentHashMap<>();
/** /**
* Load the specified shared library. * Load the specified shared library.
@ -76,67 +76,69 @@ public final class LibrariesHelper {
"Library not found: " + path); "Library not found: " + path);
} }
// return the absolute path of the library of given name by searching
// in the given array of paths.
private static Optional<Path> findLibraryPath(Path[] paths, String libName) {
return Arrays.stream(paths).
map(p -> p.resolve(System.mapLibraryName(libName))).
filter(Files::isRegularFile).map(Path::toAbsolutePath).findFirst();
}
public static LibraryLookup getDefaultLibrary() { public static LibraryLookup getDefaultLibrary() {
return LibraryLookupImpl.DEFAULT_LOOKUP; return LibraryLookupImpl.DEFAULT_LOOKUP;
} }
synchronized static LibraryLookupImpl lookup(Supplier<NativeLibrary> librarySupplier, String notFoundMsg) { static LibraryLookupImpl lookup(Supplier<NativeLibrary> librarySupplier, String notFoundMsg) {
NativeLibrary library = librarySupplier.get(); NativeLibrary library = librarySupplier.get();
if (library == null) { if (library == null) {
throw new IllegalArgumentException(notFoundMsg); throw new IllegalArgumentException(notFoundMsg);
} }
AtomicInteger refCount = loadedLibraries.computeIfAbsent(library, lib -> new AtomicInteger()); ResourceScope[] holder = new ResourceScope[1];
refCount.incrementAndGet(); try {
LibraryLookupImpl lookup = new LibraryLookupImpl(library); WeakReference<ResourceScope> scopeRef = loadedLibraries.computeIfAbsent(library, lib -> {
CleanerFactory.cleaner().register(lookup, () -> tryUnload(library)); ResourceScopeImpl s = ResourceScopeImpl.createImplicitScope();
return lookup; holder[0] = s; // keep the scope alive at least until the outer method returns
} s.addOrCleanupIfFail(ResourceScopeImpl.ResourceList.ResourceCleanup.ofRunnable(() -> {
synchronized static void tryUnload(NativeLibrary library) {
AtomicInteger refCount = loadedLibraries.get(library);
if (refCount.decrementAndGet() == 0) {
loadedLibraries.remove(library);
nativeLibraries.unload(library); nativeLibraries.unload(library);
loadedLibraries.remove(library);
}));
return new WeakReference<>(s);
});
return new LibraryLookupImpl(library, scopeRef.get());
} finally {
Reference.reachabilityFence(holder);
} }
} }
static class LibraryLookupImpl implements LibraryLookup { //Todo: in principle we could expose a scope accessor, so that users could unload libraries at will
static final class LibraryLookupImpl implements LibraryLookup {
final NativeLibrary library; final NativeLibrary library;
final MemorySegment librarySegment;
LibraryLookupImpl(NativeLibrary library) { LibraryLookupImpl(NativeLibrary library, ResourceScope scope) {
this.library = library; this.library = library;
this.librarySegment = MemoryAddress.NULL.asSegment(Long.MAX_VALUE, scope);
} }
@Override @Override
public Optional<Symbol> lookup(String name) { public final Optional<MemoryAddress> lookup(String name) {
try { try {
Objects.requireNonNull(name); Objects.requireNonNull(name);
MemoryAddress addr = MemoryAddress.ofLong(library.lookup(name)); MemoryAddress addr = MemoryAddress.ofLong(library.lookup(name));
return Optional.of(new Symbol() { // inner class - retains a link to enclosing lookup return Optional.of(librarySegment.asSlice(addr).address());
@Override
public String name() {
return name;
}
@Override
public MemoryAddress address() {
return addr;
}
});
} catch (NoSuchMethodException ex) { } catch (NoSuchMethodException ex) {
return Optional.empty(); return Optional.empty();
} }
} }
static LibraryLookup DEFAULT_LOOKUP = new LibraryLookupImpl(NativeLibraries.defaultLibrary); @Override
public final Optional<MemorySegment> lookup(String name, MemoryLayout layout) {
try {
Objects.requireNonNull(name);
Objects.requireNonNull(layout);
MemoryAddress addr = MemoryAddress.ofLong(library.lookup(name));
if (addr.toRawLongValue() % layout.byteAlignment() != 0) {
throw new IllegalArgumentException("Bad layout alignment constraints: " + layout.byteAlignment());
}
return Optional.of(librarySegment.asSlice(addr, layout.byteSize()));
} catch (NoSuchMethodException ex) {
return Optional.empty();
}
}
static LibraryLookup DEFAULT_LOOKUP = new LibraryLookupImpl(NativeLibraries.defaultLibrary, ResourceScopeImpl.GLOBAL);
} }
/* used for testing */ /* used for testing */

View file

@ -27,10 +27,10 @@ package jdk.internal.foreign;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.internal.access.foreign.UnmapperProxy; import jdk.internal.access.foreign.UnmapperProxy;
import jdk.internal.misc.ExtendedMapMode;
import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.misc.ScopedMemoryAccess;
import sun.nio.ch.FileChannelImpl; import sun.nio.ch.FileChannelImpl;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@ -40,7 +40,6 @@ import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
/** /**
* Implementation for a mapped memory segments. A mapped memory segment is a native memory segment, which * Implementation for a mapped memory segments. A mapped memory segment is a native memory segment, which
@ -54,18 +53,19 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
static ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); static ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
MappedMemorySegmentImpl(long min, UnmapperProxy unmapper, long length, int mask, MemoryScope scope) { MappedMemorySegmentImpl(long min, UnmapperProxy unmapper, long length, int mask, ResourceScopeImpl scope) {
super(min, length, mask, scope); super(min, length, mask, scope);
this.unmapper = unmapper; this.unmapper = unmapper;
} }
@Override @Override
ByteBuffer makeByteBuffer() { ByteBuffer makeByteBuffer() {
return nioAccess.newMappedByteBuffer(unmapper, min, (int)length, null, this); return nioAccess.newMappedByteBuffer(unmapper, min, (int)length, null,
scope == ResourceScopeImpl.GLOBAL ? null : this);
} }
@Override @Override
MappedMemorySegmentImpl dup(long offset, long size, int mask, MemoryScope scope) { MappedMemorySegmentImpl dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new MappedMemorySegmentImpl(min + offset, unmapper, size, mask, scope); return new MappedMemorySegmentImpl(min + offset, unmapper, size, mask, scope);
} }
@ -77,11 +77,6 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
return (MappedMemorySegmentImpl)super.asSlice(offset, newSize); return (MappedMemorySegmentImpl)super.asSlice(offset, newSize);
} }
@Override
public MappedMemorySegmentImpl withAccessModes(int accessModes) {
return (MappedMemorySegmentImpl)super.withAccessModes(accessModes);
}
@Override @Override
public boolean isMapped() { public boolean isMapped() {
return true; return true;
@ -111,9 +106,10 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
// factories // factories
public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode) throws IOException { public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode, ResourceScopeImpl scope) throws IOException {
Objects.requireNonNull(path); Objects.requireNonNull(path);
Objects.requireNonNull(mapMode); Objects.requireNonNull(mapMode);
scope.checkValidStateSlow();
if (bytesSize < 0) throw new IllegalArgumentException("Requested bytes size must be >= 0."); if (bytesSize < 0) throw new IllegalArgumentException("Requested bytes size must be >= 0.");
if (bytesOffset < 0) throw new IllegalArgumentException("Requested bytes offset must be >= 0."); if (bytesOffset < 0) throw new IllegalArgumentException("Requested bytes offset must be >= 0.");
FileSystem fs = path.getFileSystem(); FileSystem fs = path.getFileSystem();
@ -125,22 +121,31 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
UnmapperProxy unmapperProxy = ((FileChannelImpl)channelImpl).mapInternal(mapMode, bytesOffset, bytesSize); UnmapperProxy unmapperProxy = ((FileChannelImpl)channelImpl).mapInternal(mapMode, bytesOffset, bytesSize);
int modes = defaultAccessModes(bytesSize); int modes = defaultAccessModes(bytesSize);
if (mapMode == FileChannel.MapMode.READ_ONLY) { if (mapMode == FileChannel.MapMode.READ_ONLY) {
modes &= ~WRITE; modes |= READ_ONLY;
} }
if (unmapperProxy != null) { if (unmapperProxy != null) {
MemoryScope scope = MemoryScope.createConfined(null, unmapperProxy::unmap, null); AbstractMemorySegmentImpl segment = new MappedMemorySegmentImpl(unmapperProxy.address(), unmapperProxy, bytesSize,
return new MappedMemorySegmentImpl(unmapperProxy.address(), unmapperProxy, bytesSize,
modes, scope); modes, scope);
scope.addOrCleanupIfFail(new ResourceScopeImpl.ResourceList.ResourceCleanup() {
@Override
public void cleanup() {
unmapperProxy.unmap();
}
});
return segment;
} else { } else {
return new EmptyMappedMemorySegmentImpl(modes); return new EmptyMappedMemorySegmentImpl(modes, scope);
} }
} }
} }
private static OpenOption[] openOptions(FileChannel.MapMode mapMode) { private static OpenOption[] openOptions(FileChannel.MapMode mapMode) {
if (mapMode == FileChannel.MapMode.READ_ONLY) { if (mapMode == FileChannel.MapMode.READ_ONLY ||
mapMode == ExtendedMapMode.READ_ONLY_SYNC) {
return new OpenOption[] { StandardOpenOption.READ }; return new OpenOption[] { StandardOpenOption.READ };
} else if (mapMode == FileChannel.MapMode.READ_WRITE || mapMode == FileChannel.MapMode.PRIVATE) { } else if (mapMode == FileChannel.MapMode.READ_WRITE ||
mapMode == FileChannel.MapMode.PRIVATE ||
mapMode == ExtendedMapMode.READ_WRITE_SYNC) {
return new OpenOption[] { StandardOpenOption.READ, StandardOpenOption.WRITE }; return new OpenOption[] { StandardOpenOption.READ, StandardOpenOption.WRITE };
} else { } else {
throw new UnsupportedOperationException("Unsupported map mode: " + mapMode); throw new UnsupportedOperationException("Unsupported map mode: " + mapMode);
@ -149,9 +154,8 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
static class EmptyMappedMemorySegmentImpl extends MappedMemorySegmentImpl { static class EmptyMappedMemorySegmentImpl extends MappedMemorySegmentImpl {
public EmptyMappedMemorySegmentImpl(int modes) { public EmptyMappedMemorySegmentImpl(int modes, ResourceScopeImpl scope) {
super(0, null, 0, modes, super(0, null, 0, modes, scope);
MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null));
} }
@Override @Override

View file

@ -27,7 +27,10 @@ package jdk.internal.foreign;
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.incubator.foreign.ResourceScope;
import java.util.Objects; import java.util.Objects;
/** /**
@ -36,52 +39,75 @@ import java.util.Objects;
*/ */
public final class MemoryAddressImpl implements MemoryAddress { public final class MemoryAddressImpl implements MemoryAddress {
private final Object base; private final AbstractMemorySegmentImpl segment;
private final long offset; private final long offset;
public MemoryAddressImpl(Object base, long offset) { public MemoryAddressImpl(AbstractMemorySegmentImpl segment, long offset) {
this.base = base; this.segment = segment;
this.offset = offset; this.offset = offset;
} }
Object base() {
return segment != null ? segment.base() : null;
}
long offset() {
return segment != null ?
segment.min() + offset : offset;
}
// MemoryAddress methods // MemoryAddress methods
@Override
public ResourceScope scope() {
return segment != null ?
segment.scope() : ResourceScope.globalScope();
}
@Override
public MemoryAddress addOffset(long offset) {
return new MemoryAddressImpl(segment, this.offset + offset);
}
@Override @Override
public long segmentOffset(MemorySegment segment) { public long segmentOffset(MemorySegment segment) {
Objects.requireNonNull(segment); Objects.requireNonNull(segment);
AbstractMemorySegmentImpl segmentImpl = (AbstractMemorySegmentImpl)segment; AbstractMemorySegmentImpl segmentImpl = (AbstractMemorySegmentImpl)segment;
if (segmentImpl.base() != base) { if (segmentImpl.base() != base()) {
throw new IllegalArgumentException("Invalid segment: " + segment); throw new IllegalArgumentException("Incompatible segment: " + segment);
} }
return offset - segmentImpl.min(); return offset() - segmentImpl.min();
}
@Override
public boolean isNative() {
return base() == null;
} }
@Override @Override
public long toRawLongValue() { public long toRawLongValue() {
if (base != null) { if (segment != null) {
if (segment.base() != null) {
throw new UnsupportedOperationException("Not a native address"); throw new UnsupportedOperationException("Not a native address");
} }
return offset; segment.checkValidState();
} }
return offset();
@Override
public MemoryAddress addOffset(long bytes) {
return new MemoryAddressImpl(base, offset + bytes);
} }
// Object methods // Object methods
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(base, offset); return Objects.hash(base(), offset());
} }
@Override @Override
public boolean equals(Object that) { public boolean equals(Object that) {
if (that instanceof MemoryAddressImpl) { if (that instanceof MemoryAddressImpl) {
MemoryAddressImpl addr = (MemoryAddressImpl)that; MemoryAddressImpl addr = (MemoryAddressImpl)that;
return Objects.equals(base, addr.base) && return Objects.equals(base(), addr.base()) &&
offset == addr.offset; offset() == addr.offset();
} else { } else {
return false; return false;
} }
@ -89,23 +115,38 @@ public final class MemoryAddressImpl implements MemoryAddress {
@Override @Override
public String toString() { public String toString() {
return "MemoryAddress{ base: " + base + " offset=0x" + Long.toHexString(offset) + " }"; return "MemoryAddress{ base: " + base() + " offset=0x" + Long.toHexString(offset()) + " }";
} }
@Override @Override
public MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, Object attachment) { @CallerSensitive
Utils.checkRestrictedAccess("MemoryAddress.asSegmentRestricted"); public final MemorySegment asSegment(long bytesSize, ResourceScope scope) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
return asSegment(bytesSize, null, scope);
}
@Override
@CallerSensitive
public final MemorySegment asSegment(long bytesSize, Runnable cleanupAction, ResourceScope scope) {
Reflection.ensureNativeAccess(Reflection.getCallerClass());
Objects.requireNonNull(scope);
if (bytesSize <= 0) { if (bytesSize <= 0) {
throw new IllegalArgumentException("Invalid size : " + bytesSize); throw new IllegalArgumentException("Invalid size : " + bytesSize);
} }
return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(this, bytesSize, cleanupAction, attachment); return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(this, bytesSize,
cleanupAction,
(ResourceScopeImpl) scope);
} }
public static MemorySegment ofLongUnchecked(long value) { public static MemorySegment ofLongUnchecked(long value) {
return ofLongUnchecked(value, Long.MAX_VALUE); return ofLongUnchecked(value, Long.MAX_VALUE);
} }
public static MemorySegment ofLongUnchecked(long value, long byteSize, ResourceScopeImpl resourceScope) {
return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(MemoryAddress.ofLong(value), byteSize, null, resourceScope);
}
public static MemorySegment ofLongUnchecked(long value, long byteSize) { public static MemorySegment ofLongUnchecked(long value, long byteSize) {
return MemoryAddress.ofLong(value).asSegmentRestricted(byteSize).share(); return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(MemoryAddress.ofLong(value), byteSize, null, ResourceScopeImpl.GLOBAL);
} }
} }

View file

@ -1,307 +0,0 @@
/*
* Copyright (c) 2019, 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;
import jdk.internal.misc.ScopedMemoryAccess;
import jdk.internal.ref.PhantomCleanable;
import jdk.internal.vm.annotation.ForceInline;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.util.Objects;
/**
* This class manages the temporal bounds associated with a memory segment as well
* as thread confinement. A scope has a liveness bit, which is updated when the scope is closed
* (this operation is triggered by {@link AbstractMemorySegmentImpl#close()}). This bit is consulted prior
* to memory access (see {@link #checkValidState()}).
* There are two kinds of memory scope: confined memory scope and shared memory scope.
* A confined memory scope has an associated owner thread that confines some operations to
* associated owner thread such as {@link #close()} or {@link #checkValidState()}.
* Shared scopes do not feature an owner thread - meaning their operations can be called, in a racy
* manner, by multiple threads. To guarantee temporal safety in the presence of concurrent thread,
* shared scopes use a more sophisticated synchronization mechanism, which guarantees that no concurrent
* access is possible when a scope is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}).
*/
abstract class MemoryScope implements ScopedMemoryAccess.Scope {
static final Runnable DUMMY_CLEANUP_ACTION = () -> { };
private MemoryScope(Object ref, Runnable cleanupAction, Cleaner cleaner) {
Objects.requireNonNull(cleanupAction);
this.ref = ref;
this.cleanupAction = cleanupAction;
this.scopeCleanable = cleaner != null ?
new ScopeCleanable(this, cleaner, cleanupAction) :
null;
}
/**
* Creates a confined memory scope with given attachment and cleanup action. The returned scope
* is assumed to be confined on the current thread.
* @param ref an optional reference to an instance that needs to be kept reachable
* @param cleanupAction a cleanup action to be executed when returned scope is closed
* @return a confined memory scope
*/
static MemoryScope createConfined(Object ref, Runnable cleanupAction, Cleaner cleaner) {
return new ConfinedScope(Thread.currentThread(), ref, cleanupAction, cleaner);
}
/**
* Creates a shared memory scope with given attachment and cleanup action.
* @param ref an optional reference to an instance that needs to be kept reachable
* @param cleanupAction a cleanup action to be executed when returned scope is closed
* @return a shared memory scope
*/
static MemoryScope createShared(Object ref, Runnable cleanupAction, Cleaner cleaner) {
return new SharedScope(ref, cleanupAction, cleaner);
}
protected final Object ref;
protected final ScopeCleanable scopeCleanable;
protected final Runnable cleanupAction;
/**
* Closes this scope, executing any cleanup action (where provided).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
final void close() {
try {
justClose();
cleanupAction.run();
if (scopeCleanable != null) {
scopeCleanable.clear();
}
} finally {
Reference.reachabilityFence(this);
}
}
abstract void justClose();
/**
* Duplicates this scope with given new "owner" thread and {@link #close() closes} it.
* @param newOwner new owner thread of the returned memory scope
* @return a new confined scope, which is a duplicate of this scope, but with a new owner thread.
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
final MemoryScope confineTo(Thread newOwner) {
try {
justClose();
if (scopeCleanable != null) {
scopeCleanable.clear();
}
return new ConfinedScope(newOwner, ref, cleanupAction, scopeCleanable != null ?
scopeCleanable.cleaner : null);
} finally {
Reference.reachabilityFence(this);
}
}
/**
* Duplicates this scope with given new "owner" thread and {@link #close() closes} it.
* @return a new shared scope, which is a duplicate of this scope.
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread,
* or if this is already a shared scope.
*/
final MemoryScope share() {
try {
justClose();
if (scopeCleanable != null) {
scopeCleanable.clear();
}
return new SharedScope(ref, cleanupAction, scopeCleanable != null ?
scopeCleanable.cleaner : null);
} finally {
Reference.reachabilityFence(this);
}
}
final MemoryScope cleanable(Cleaner cleaner) {
if (scopeCleanable != null) {
throw new IllegalStateException("Already registered with a cleaner");
}
try {
justClose();
return ownerThread() == null ?
new SharedScope(ref, cleanupAction, cleaner) :
new ConfinedScope(ownerThread(), ref, cleanupAction, cleaner);
} finally {
Reference.reachabilityFence(this);
}
}
/**
* Returns "owner" thread of this scope.
* @return owner thread (or null for a shared scope)
*/
public abstract Thread ownerThread();
/**
* Returns true, if this scope is still alive. This method may be called in any thread.
* @return {@code true} if this scope is not closed yet.
*/
public abstract boolean isAlive();
/**
* Checks that this scope is still alive (see {@link #isAlive()}).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
public abstract void checkValidState();
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
/**
* A confined scope, which features an owner thread. The liveness check features an additional
* confinement check - that is, calling any operation on this scope from a thread other than the
* owner thread will result in an exception. Because of this restriction, checking the liveness bit
* can be performed in plain mode (see {@link #checkAliveRaw(MemoryScope)}).
*/
static class ConfinedScope extends MemoryScope {
private boolean closed; // = false
final Thread owner;
public ConfinedScope(Thread owner, Object ref, Runnable cleanupAction, Cleaner cleaner) {
super(ref, cleanupAction, cleaner);
this.owner = owner;
}
@ForceInline
public final void checkValidState() {
if (owner != Thread.currentThread()) {
throw new IllegalStateException("Attempted access outside owning thread");
}
if (closed) {
throw ScopedAccessError.INSTANCE;
}
}
@Override
public boolean isAlive() {
return !closed;
}
void justClose() {
checkValidState();
closed = true;
}
@Override
public Thread ownerThread() {
return owner;
}
}
/**
* A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
* (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
* (ii) no other thread is accessing the memory associated with this scope while the segment is being
* closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
* is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
* Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
* checking the liveness bit upon access can be performed in plain mode (see {@link #checkAliveRaw(MemoryScope)}),
* as in the confined case.
*/
static class SharedScope extends MemoryScope {
static ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
final static int ALIVE = 0;
final static int CLOSING = 1;
final static int CLOSED = 2;
int state = ALIVE;
private static final VarHandle STATE;
static {
try {
STATE = MethodHandles.lookup().findVarHandle(SharedScope.class, "state", int.class);
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
SharedScope(Object ref, Runnable cleanupAction, Cleaner cleaner) {
super(ref, cleanupAction, cleaner);
}
@Override
public Thread ownerThread() {
return null;
}
@Override
public void checkValidState() {
if (state != ALIVE) {
throw ScopedAccessError.INSTANCE;
}
}
void justClose() {
if (!STATE.compareAndSet(this, ALIVE, CLOSING)) {
throw new IllegalStateException("Already closed");
}
boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
STATE.setVolatile(this, success ? CLOSED : ALIVE);
if (!success) {
throw new IllegalStateException("Cannot close while another thread is accessing the segment");
}
}
@Override
public boolean isAlive() {
return (int)STATE.getVolatile(this) != CLOSED;
}
}
static class ScopeCleanable extends PhantomCleanable<MemoryScope> {
final Cleaner cleaner;
final Runnable cleanupAction;
public ScopeCleanable(MemoryScope referent, Cleaner cleaner, Runnable cleanupAction) {
super(referent, cleaner);
this.cleaner = cleaner;
this.cleanupAction = cleanupAction;
}
@Override
protected void performCleanup() {
cleanupAction.run();
}
}
}

View file

@ -28,6 +28,8 @@ package jdk.internal.foreign;
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.misc.Unsafe; import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM; import jdk.internal.misc.VM;
import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.ForceInline;
@ -41,34 +43,40 @@ import java.nio.ByteBuffer;
*/ */
public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl { public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl {
public static final MemorySegment EVERYTHING = makeNativeSegmentUnchecked(MemoryAddress.NULL, Long.MAX_VALUE, MemoryScope.DUMMY_CLEANUP_ACTION, null) public static final MemorySegment EVERYTHING = makeNativeSegmentUnchecked(MemoryAddress.NULL, Long.MAX_VALUE, null, ResourceScopeImpl.GLOBAL);
.share()
.withAccessModes(READ | WRITE);
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final Unsafe unsafe = Unsafe.getUnsafe();
public static final SegmentAllocator IMPLICIT_ALLOCATOR = (size, align) -> MemorySegment.allocateNative(size, align, ResourceScope.newImplicitScope());
// The maximum alignment supported by malloc - typically 16 on // The maximum alignment supported by malloc - typically 16 on
// 64-bit platforms and 8 on 32-bit platforms. // 64-bit platforms and 8 on 32-bit platforms.
private final static long MAX_MALLOC_ALIGN = Unsafe.ADDRESS_SIZE == 4 ? 8 : 16; private static final long MAX_MALLOC_ALIGN = Unsafe.ADDRESS_SIZE == 4 ? 8 : 16;
private static final boolean skipZeroMemory = GetBooleanAction.privilegedGetProperty("jdk.internal.foreign.skipZeroMemory"); private static final boolean skipZeroMemory = GetBooleanAction.privilegedGetProperty("jdk.internal.foreign.skipZeroMemory");
final long min; final long min;
@ForceInline @ForceInline
NativeMemorySegmentImpl(long min, long length, int mask, MemoryScope scope) { NativeMemorySegmentImpl(long min, long length, int mask, ResourceScopeImpl scope) {
super(length, mask, scope); super(length, mask, scope);
this.min = min; this.min = min;
} }
@Override @Override
NativeMemorySegmentImpl dup(long offset, long size, int mask, MemoryScope scope) { NativeMemorySegmentImpl dup(long offset, long size, int mask, ResourceScopeImpl scope) {
return new NativeMemorySegmentImpl(min + offset, size, mask, scope); return new NativeMemorySegmentImpl(min + offset, size, mask, scope);
} }
@Override @Override
ByteBuffer makeByteBuffer() { ByteBuffer makeByteBuffer() {
return nioAccess.newDirectByteBuffer(min(), (int) this.length, null, this); return nioAccess.newDirectByteBuffer(min(), (int) this.length, null,
scope == ResourceScopeImpl.GLOBAL ? null : this);
}
@Override
public boolean isNative() {
return true;
} }
@Override @Override
@ -83,7 +91,8 @@ public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl {
// factories // factories
public static MemorySegment makeNativeSegment(long bytesSize, long alignmentBytes) { public static MemorySegment makeNativeSegment(long bytesSize, long alignmentBytes, ResourceScopeImpl scope) {
scope.checkValidStateSlow();
if (VM.isDirectMemoryPageAligned()) { if (VM.isDirectMemoryPageAligned()) {
alignmentBytes = Math.max(alignmentBytes, nioAccess.pageSize()); alignmentBytes = Math.max(alignmentBytes, nioAccess.pageSize());
} }
@ -98,12 +107,15 @@ public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl {
unsafe.setMemory(buf, alignedSize, (byte)0); unsafe.setMemory(buf, alignedSize, (byte)0);
} }
long alignedBuf = Utils.alignUp(buf, alignmentBytes); long alignedBuf = Utils.alignUp(buf, alignmentBytes);
MemoryScope scope = MemoryScope.createConfined(null, () -> { AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(buf, alignedSize,
defaultAccessModes(alignedSize), scope);
scope.addOrCleanupIfFail(new ResourceScopeImpl.ResourceList.ResourceCleanup() {
@Override
public void cleanup() {
unsafe.freeMemory(buf); unsafe.freeMemory(buf);
nioAccess.unreserveMemory(alignedSize, bytesSize); nioAccess.unreserveMemory(alignedSize, bytesSize);
}, null); }
MemorySegment segment = new NativeMemorySegmentImpl(buf, alignedSize, });
defaultAccessModes(alignedSize), scope);
if (alignedSize != bytesSize) { if (alignedSize != bytesSize) {
long delta = alignedBuf - buf; long delta = alignedBuf - buf;
segment = segment.asSlice(delta, bytesSize); segment = segment.asSlice(delta, bytesSize);
@ -111,8 +123,12 @@ public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl {
return segment; return segment;
} }
public static MemorySegment makeNativeSegmentUnchecked(MemoryAddress min, long bytesSize, Runnable cleanupAction, Object ref) { public static MemorySegment makeNativeSegmentUnchecked(MemoryAddress min, long bytesSize, Runnable cleanupAction, ResourceScopeImpl scope) {
return new NativeMemorySegmentImpl(min.toRawLongValue(), bytesSize, defaultAccessModes(bytesSize), scope.checkValidStateSlow();
MemoryScope.createConfined(ref, cleanupAction == null ? MemoryScope.DUMMY_CLEANUP_ACTION : cleanupAction, null)); AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(min.toRawLongValue(), bytesSize, defaultAccessModes(bytesSize), scope);
if (cleanupAction != null) {
scope.addCloseAction(cleanupAction);
}
return segment;
} }
} }

View file

@ -32,7 +32,6 @@ import jdk.incubator.foreign.ValueLayout;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static jdk.incubator.foreign.MemoryLayouts.ADDRESS;
public class PlatformLayouts { public class PlatformLayouts {
public static <Z extends MemoryLayout> Z pick(Z sysv, Z win64, Z aarch64) { public static <Z extends MemoryLayout> Z pick(Z sysv, Z win64, Z aarch64) {
@ -51,42 +50,42 @@ public class PlatformLayouts {
} }
private static ValueLayout ofChar(ByteOrder order, long bitSize) { private static ValueLayout ofChar(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.CHAR); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.CHAR);
} }
private static ValueLayout ofShort(ByteOrder order, long bitSize) { private static ValueLayout ofShort(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.SHORT); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.SHORT);
} }
private static ValueLayout ofInt(ByteOrder order, long bitSize) { private static ValueLayout ofInt(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.INT); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.INT);
} }
private static ValueLayout ofLong(ByteOrder order, long bitSize) { private static ValueLayout ofLong(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.LONG); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.LONG);
} }
private static ValueLayout ofLongLong(ByteOrder order, long bitSize) { private static ValueLayout ofLongLong(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.LONG_LONG); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.LONG_LONG);
} }
private static ValueLayout ofFloat(ByteOrder order, long bitSize) { private static ValueLayout ofFloat(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.FLOAT); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.FLOAT);
} }
private static ValueLayout ofDouble(ByteOrder order, long bitSize) { private static ValueLayout ofDouble(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.DOUBLE); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.DOUBLE);
} }
private static ValueLayout ofPointer(ByteOrder order, long bitSize) { private static ValueLayout ofPointer(ByteOrder order, long bitSize) {
return MemoryLayout.ofValueBits(bitSize, order) return MemoryLayout.valueLayout(bitSize, order)
.withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.POINTER); .withAttribute(CLinker.TypeKind.ATTR_NAME, CLinker.TypeKind.POINTER);
} }
@ -162,7 +161,7 @@ public class PlatformLayouts {
* The name of the layout attribute (see {@link MemoryLayout#attributes()} used to mark variadic parameters. The * The name of the layout attribute (see {@link MemoryLayout#attributes()} used to mark variadic parameters. The
* attribute value must be a boolean. * attribute value must be a boolean.
*/ */
public final static String VARARGS_ATTRIBUTE_NAME = "abi/windows/varargs"; public static final String VARARGS_ATTRIBUTE_NAME = "abi/windows/varargs";
/** /**
* The {@code char} native type. * The {@code char} native type.

View file

@ -0,0 +1,322 @@
/*
* Copyright (c) 2019, 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;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.misc.ScopedMemoryAccess;
import jdk.internal.ref.CleanerFactory;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.util.Objects;
/**
* This class manages the temporal bounds associated with a memory segment as well
* as thread confinement. A scope has a liveness bit, which is updated when the scope is closed
* (this operation is triggered by {@link ResourceScope#close()}). This bit is consulted prior
* to memory access (see {@link #checkValidState()}).
* There are two kinds of memory scope: confined memory scope and shared memory scope.
* A confined memory scope has an associated owner thread that confines some operations to
* associated owner thread such as {@link #close()} or {@link #checkValidState()}.
* Shared scopes do not feature an owner thread - meaning their operations can be called, in a racy
* manner, by multiple threads. To guarantee temporal safety in the presence of concurrent thread,
* shared scopes use a more sophisticated synchronization mechanism, which guarantees that no concurrent
* access is possible when a scope is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}).
*/
public abstract non-sealed class ResourceScopeImpl implements ResourceScope, ScopedMemoryAccess.Scope, SegmentAllocator {
final ResourceList resourceList;
@Override
public void addCloseAction(Runnable runnable) {
Objects.requireNonNull(runnable);
addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable));
}
@Override
public boolean isImplicit() {
return false;
}
/**
* Add a cleanup action. If a failure occurred (because of a add vs. close race), call the cleanup action.
* This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_
* we register the cleanup (free/munmap) against the scope; so, if registration fails, we still have to
* cleanup memory. From the perspective of the client, such a failure would manifest as a factory
* returning a segment that is already "closed" - which is always possible anyway (e.g. if the scope
* is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the
* new segment to the client). For this reason, it's not worth adding extra complexity to the segment
* initialization logic here - and using an optimistic logic works well in practice.
*/
public void addOrCleanupIfFail(ResourceList.ResourceCleanup resource) {
try {
addInternal(resource);
} catch (Throwable ex) {
resource.cleanup();
}
}
void addInternal(ResourceList.ResourceCleanup resource) {
try {
checkValidStateSlow();
resourceList.add(resource);
} catch (ScopedMemoryAccess.Scope.ScopedAccessError err) {
throw new IllegalStateException("Already closed");
}
}
protected ResourceScopeImpl(Cleaner cleaner, ResourceList resourceList) {
this.resourceList = resourceList;
if (cleaner != null) {
cleaner.register(this, resourceList);
}
}
public static ResourceScopeImpl createImplicitScope() {
return new ImplicitScopeImpl(CleanerFactory.cleaner());
}
public static ResourceScopeImpl createConfined(Thread thread, Cleaner cleaner) {
return new ConfinedScope(thread, cleaner);
}
/**
* Creates a confined memory scope with given attachment and cleanup action. The returned scope
* is assumed to be confined on the current thread.
* @return a confined memory scope
*/
public static ResourceScopeImpl createConfined(Cleaner cleaner) {
return new ConfinedScope(Thread.currentThread(), cleaner);
}
/**
* Creates a shared memory scope with given attachment and cleanup action.
* @return a shared memory scope
*/
public static ResourceScopeImpl createShared(Cleaner cleaner) {
return new SharedScope(cleaner);
}
private final void release0(HandleImpl handle) {
try {
Objects.requireNonNull(handle);
if (handle.scope() != this) {
throw new IllegalArgumentException("Cannot release an handle acquired from another scope");
}
handle.release();
} finally {
Reference.reachabilityFence(this);
}
}
@Override
public final void release(ResourceScope.Handle handle) {
release0((HandleImpl)handle);
}
@Override
public final void release(ScopedMemoryAccess.Scope.Handle handle) {
release0((HandleImpl)handle);
}
@Override
public abstract HandleImpl acquire();
/**
* Internal interface used to implement resource scope handles.
*/
public non-sealed interface HandleImpl extends ResourceScope.Handle, ScopedMemoryAccess.Scope.Handle {
@Override
ResourceScopeImpl scope();
void release();
}
/**
* Closes this scope, executing any cleanup action (where provided).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
public void close() {
try {
justClose();
resourceList.cleanup();
} finally {
Reference.reachabilityFence(this);
}
}
abstract void justClose();
/**
* Returns "owner" thread of this scope.
* @return owner thread (or null for a shared scope)
*/
public abstract Thread ownerThread();
/**
* Returns true, if this scope is still alive. This method may be called in any thread.
* @return {@code true} if this scope is not closed yet.
*/
public abstract boolean isAlive();
/**
* This is a faster version of {@link #checkValidStateSlow()}, which is called upon memory access, and which
* relies on invariants associated with the memory scope implementations (typically, volatile access
* to the closed state bit is replaced with plain access, and ownership check is removed where not needed.
* Should be used with care.
*/
public abstract void checkValidState();
/**
* Checks that this scope is still alive (see {@link #isAlive()}).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
public final void checkValidStateSlow() {
if (ownerThread() != null && Thread.currentThread() != ownerThread()) {
throw new IllegalStateException("Attempted access outside owning thread");
} else if (!isAlive()) {
throw new IllegalStateException("Already closed");
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
/**
* Allocates a segment using this scope. Used by {@link SegmentAllocator#ofScope(ResourceScope)}.
*/
@Override
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
return MemorySegment.allocateNative(bytesSize, bytesAlignment, this);
}
/**
* A non-closeable, shared scope. Similar to a shared scope, but its {@link #close()} method throws unconditionally.
* In addition, non-closeable scopes feature a much simpler scheme for generating resource scope handles, where
* the scope itself also acts as a resource scope handle and is returned by {@link #acquire()}.
*/
static class ImplicitScopeImpl extends SharedScope implements HandleImpl {
public ImplicitScopeImpl(Cleaner cleaner) {
super(cleaner);
}
@Override
public HandleImpl acquire() {
return this;
}
@Override
public boolean isImplicit() {
return true;
}
@Override
public void close() {
throw new UnsupportedOperationException("Scope cannot be closed");
}
@Override
public void release() {
// do nothing
}
@Override
public ResourceScopeImpl scope() {
return this;
}
}
/**
* The global, always alive, non-closeable, shared scope. This is like a {@link ImplicitScopeImpl non-closeable scope},
* except that the operation which adds new resources to the global scope does nothing: as the scope can never
* become not-alive, there is nothing to track.
*/
public static final ResourceScopeImpl GLOBAL = new ImplicitScopeImpl( null) {
@Override
void addInternal(ResourceList.ResourceCleanup resource) {
// do nothing
}
};
/**
* A list of all cleanup actions associated with a resource scope. Cleanup actions are modelled as instances
* of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a scope
* is shared or confined, different implementations of this class will be used, see {@link ConfinedScope.ConfinedResourceList}
* and {@link SharedScope.SharedResourceList}.
*/
public abstract static class ResourceList implements Runnable {
ResourceCleanup fst;
abstract void add(ResourceCleanup cleanup);
abstract void cleanup();
public final void run() {
cleanup(); // cleaner interop
}
static void cleanup(ResourceCleanup first) {
ResourceCleanup current = first;
while (current != null) {
current.cleanup();
current = current.next;
}
}
public static abstract class ResourceCleanup {
ResourceCleanup next;
public abstract void cleanup();
static final ResourceCleanup CLOSED_LIST = new ResourceCleanup() {
@Override
public void cleanup() {
throw new IllegalStateException("This resource list has already been closed!");
}
};
static ResourceCleanup ofRunnable(Runnable cleanupAction) {
return new ResourceCleanup() {
@Override
public void cleanup() {
cleanupAction.run();
}
};
}
}
}
}

View file

@ -0,0 +1,196 @@
/*
* Copyright (c) 2021, 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;
import jdk.incubator.foreign.ResourceScope;
import jdk.internal.misc.ScopedMemoryAccess;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
* (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
* (ii) no other thread is accessing the memory associated with this scope while the segment is being
* closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
* is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
* Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
* checking the liveness bit upon access can be performed in plain mode, as in the confined case.
*/
class SharedScope extends ResourceScopeImpl {
private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
private static final int ALIVE = 0;
private static final int CLOSING = -1;
private static final int CLOSED = -2;
private static final int MAX_FORKS = Integer.MAX_VALUE;
private int state = ALIVE;
private static final VarHandle STATE;
static {
try {
STATE = MethodHandles.lookup().findVarHandle(jdk.internal.foreign.SharedScope.class, "state", int.class);
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
SharedScope(Cleaner cleaner) {
super(cleaner, new SharedResourceList());
}
@Override
public Thread ownerThread() {
return null;
}
@Override
public void checkValidState() {
if (state < ALIVE) {
throw ScopedAccessError.INSTANCE;
}
}
@Override
public HandleImpl acquire() {
int value;
do {
value = (int) STATE.getVolatile(this);
if (value < ALIVE) {
//segment is not alive!
throw new IllegalStateException("Already closed");
} else if (value == MAX_FORKS) {
//overflow
throw new IllegalStateException("Segment acquire limit exceeded");
}
} while (!STATE.compareAndSet(this, value, value + 1));
return new SharedHandle();
}
void justClose() {
int prevState = (int) STATE.compareAndExchange(this, ALIVE, CLOSING);
if (prevState < 0) {
throw new IllegalStateException("Already closed");
} else if (prevState != ALIVE) {
throw new IllegalStateException("Scope is acquired by " + prevState + " locks");
}
boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
STATE.setVolatile(this, success ? CLOSED : ALIVE);
if (!success) {
throw new IllegalStateException("Cannot close while another thread is accessing the segment");
}
}
@Override
public boolean isAlive() {
return (int) STATE.getVolatile(this) != CLOSED;
}
/**
* A shared resource list; this implementation has to handle add vs. add races, as well as add vs. cleanup races.
*/
static class SharedResourceList extends ResourceList {
static final VarHandle FST;
static {
try {
FST = MethodHandles.lookup().findVarHandle(ResourceList.class, "fst", ResourceCleanup.class);
} catch (Throwable ex) {
throw new ExceptionInInitializerError();
}
}
@Override
void add(ResourceCleanup cleanup) {
while (true) {
ResourceCleanup prev = (ResourceCleanup) FST.getAcquire(this);
cleanup.next = prev;
ResourceCleanup newSegment = (ResourceCleanup) FST.compareAndExchangeRelease(this, prev, cleanup);
if (newSegment == ResourceCleanup.CLOSED_LIST) {
// too late
throw new IllegalStateException("Already closed");
} else if (newSegment == prev) {
return; //victory
}
// keep trying
}
}
void cleanup() {
// At this point we are only interested about add vs. close races - not close vs. close
// (because MemoryScope::justClose ensured that this thread won the race to close the scope).
// So, the only "bad" thing that could happen is that some other thread adds to this list
// while we're closing it.
if (FST.getAcquire(this) != ResourceCleanup.CLOSED_LIST) {
//ok now we're really closing down
ResourceCleanup prev = null;
while (true) {
prev = (ResourceCleanup) FST.getAcquire(this);
// no need to check for DUMMY, since only one thread can get here!
if (FST.weakCompareAndSetRelease(this, prev, ResourceCleanup.CLOSED_LIST)) {
break;
}
}
cleanup(prev);
} else {
throw new IllegalStateException("Attempt to cleanup an already closed resource list");
}
}
}
/**
* A shared resource scope handle; this implementation has to handle close vs. close races.
*/
class SharedHandle implements HandleImpl {
final AtomicBoolean released = new AtomicBoolean(false);
@Override
public ResourceScopeImpl scope() {
return SharedScope.this;
}
@Override
public void release() {
if (released.compareAndSet(false, true)) {
int value;
do {
value = (int) STATE.getVolatile(jdk.internal.foreign.SharedScope.this);
if (value <= ALIVE) {
//cannot get here - we can't close segment twice
throw new IllegalStateException("Already closed");
}
} while (!STATE.compareAndSet(jdk.internal.foreign.SharedScope.this, value, value - 1));
}
}
}
}

View file

@ -44,14 +44,10 @@ import static sun.security.action.GetPropertyAction.*;
* This class contains misc helper functions to support creation of memory segments. * This class contains misc helper functions to support creation of memory segments.
*/ */
public final class Utils { public final class Utils {
// used when testing invoke exact behavior of memory access handles // used when testing invoke exact behavior of memory access handles
private static final boolean SHOULD_ADAPT_HANDLES private static final boolean SHOULD_ADAPT_HANDLES
= Boolean.parseBoolean(privilegedGetProperty("jdk.internal.foreign.SHOULD_ADAPT_HANDLES", "true")); = Boolean.parseBoolean(privilegedGetProperty("jdk.internal.foreign.SHOULD_ADAPT_HANDLES", "true"));
private static final String foreignRestrictedAccess = Optional.ofNullable(VM.getSavedProperty("foreign.restricted"))
.orElse("deny");
private static final MethodHandle SEGMENT_FILTER; private static final MethodHandle SEGMENT_FILTER;
public static final MethodHandle MH_bitsToBytesOrThrowForOffset; public static final MethodHandle MH_bitsToBytesOrThrowForOffset;
@ -107,27 +103,6 @@ public final class Utils {
return (AbstractMemorySegmentImpl)segment; return (AbstractMemorySegmentImpl)segment;
} }
public static void checkRestrictedAccess(String method) {
switch (foreignRestrictedAccess) {
case "deny" -> throwIllegalAccessError(foreignRestrictedAccess, method);
case "warn" -> System.err.println("WARNING: Accessing restricted foreign method: " + method);
case "debug" -> {
StringBuilder sb = new StringBuilder("DEBUG: restricted foreign method: \" + method");
StackWalker.getInstance().forEach(f -> sb.append(System.lineSeparator())
.append("\tat ")
.append(f));
System.err.println(sb.toString());
}
case "permit" -> {}
default -> throwIllegalAccessError(foreignRestrictedAccess, method);
}
}
private static void throwIllegalAccessError(String value, String method) {
throw new IllegalAccessError("Illegal access to restricted foreign method: " + method +
" ; system property 'foreign.restricted' is set to '" + value + "'");
}
public static void checkPrimitiveCarrierCompat(Class<?> carrier, MemoryLayout layout) { public static void checkPrimitiveCarrierCompat(Class<?> carrier, MemoryLayout layout) {
checkLayoutType(layout, ValueLayout.class); checkLayoutType(layout, ValueLayout.class);
if (!isValidPrimitiveCarrier(carrier)) if (!isValidPrimitiveCarrier(carrier))
@ -150,5 +125,4 @@ public final class Utils {
if (!layoutType.isInstance(layout)) if (!layoutType.isInstance(layout))
throw new IllegalArgumentException("Expected a " + layoutType.getSimpleName() + ": " + layout); throw new IllegalArgumentException("Expected a " + layoutType.getSimpleName() + ": " + layout);
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -28,11 +28,13 @@ import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryHandles; import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.foreign.MemoryAddressImpl; import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.ResourceScopeImpl;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
@ -44,7 +46,6 @@ import java.nio.ByteOrder;
import static java.lang.invoke.MethodHandles.collectArguments; import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.filterArguments; import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.insertArguments; import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.invoke.MethodType.methodType; import static java.lang.invoke.MethodType.methodType;
/** /**
@ -218,16 +219,99 @@ public abstract class Binding {
MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "address", MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "address",
methodType(MemoryAddress.class)); methodType(MemoryAddress.class));
MH_COPY_BUFFER = lookup.findStatic(Binding.Copy.class, "copyBuffer", MH_COPY_BUFFER = lookup.findStatic(Binding.Copy.class, "copyBuffer",
methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class)); methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, Context.class));
MH_ALLOCATE_BUFFER = lookup.findStatic(Binding.Allocate.class, "allocateBuffer", MH_ALLOCATE_BUFFER = lookup.findStatic(Binding.Allocate.class, "allocateBuffer",
methodType(MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class)); methodType(MemorySegment.class, long.class, long.class, Context.class));
MH_TO_SEGMENT = lookup.findStatic(Binding.ToSegment.class, "toSegment", MH_TO_SEGMENT = lookup.findStatic(Binding.ToSegment.class, "toSegment",
methodType(MemorySegment.class, MemoryAddress.class, long.class)); methodType(MemorySegment.class, MemoryAddress.class, long.class, Context.class));
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/**
* A binding context is used as an helper to carry out evaluation of certain bindings; for instance,
* it helps {@link Allocate} bindings, by providing the {@link SegmentAllocator} that should be used for
* the allocation operation, or {@link ToSegment} bindings, by providing the {@link ResourceScope} that
* should be used to create an unsafe struct from a memory address.
*/
public static class Context implements AutoCloseable {
private final SegmentAllocator allocator;
private final ResourceScope scope;
private Context(SegmentAllocator allocator, ResourceScope scope) {
this.allocator = allocator;
this.scope = scope;
}
public SegmentAllocator allocator() {
return allocator;
}
public ResourceScope scope() {
return scope;
}
@Override
public void close() {
scope().close();
}
/**
* Create a binding context from given native scope.
*/
public static Context ofBoundedAllocator(long size) {
ResourceScope scope = ResourceScope.newConfinedScope();
return new Context(SegmentAllocator.arenaAllocator(size, scope), scope);
}
/**
* Create a binding context from given segment allocator. The resulting context will throw when
* the context's scope is accessed.
*/
public static Context ofAllocator(SegmentAllocator allocator) {
return new Context(allocator, null) {
@Override
public ResourceScope scope() {
throw new UnsupportedOperationException();
}
};
}
/**
* Create a binding context from given scope. The resulting context will throw when
* the context's allocator is accessed.
*/
public static Context ofScope() {
ResourceScope scope = ResourceScope.newConfinedScope();
return new Context(null, scope) {
@Override
public SegmentAllocator allocator() { throw new UnsupportedOperationException(); }
};
}
/**
* Dummy binding context. Throws exceptions when attempting to access scope, return a throwing allocator, and has
* an idempotent {@link #close()}.
*/
public static final Context DUMMY = new Context(null, null) {
@Override
public SegmentAllocator allocator() {
return SharedUtils.THROWING_ALLOCATOR;
}
@Override
public ResourceScope scope() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
// do nothing
}
};
}
enum Tag { enum Tag {
VM_STORE, VM_STORE,
VM_LOAD, VM_LOAD,
@ -255,31 +339,10 @@ public abstract class Binding {
public abstract void verify(Deque<Class<?>> stack); public abstract void verify(Deque<Class<?>> stack);
public abstract void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public abstract void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator); BindingInterpreter.LoadFunc loadFunc, Context context);
public abstract MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos); 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) { private static void checkType(Class<?> type) {
if (!type.isPrimitive() || type == void.class || type == boolean.class) if (!type.isPrimitive() || type == void.class || type == boolean.class)
throw new IllegalArgumentException("Illegal type: " + type); throw new IllegalArgumentException("Illegal type: " + type);
@ -477,7 +540,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
storeFunc.store(storage(), type(), stack.pop()); storeFunc.store(storage(), type(), stack.pop());
} }
@ -512,7 +575,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(loadFunc.load(storage(), type())); stack.push(loadFunc.load(storage(), type()));
} }
@ -564,7 +627,10 @@ public abstract class Binding {
} }
public VarHandle varHandle() { public VarHandle varHandle() {
return MemoryHandles.insertCoordinates(MemoryHandles.varHandle(type, ByteOrder.nativeOrder()), 1, offset); // alignment is set to 1 byte here to avoid exceptions for cases where we do super word
// copies of e.g. 2 int fields of a struct as a single long, while the struct is only
// 4-byte-aligned (since it only contains ints)
return MemoryHandles.insertCoordinates(MemoryHandles.varHandle(type, 1, ByteOrder.nativeOrder()), 1, offset);
} }
} }
@ -589,7 +655,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
Object value = stack.pop(); Object value = stack.pop();
MemorySegment operand = (MemorySegment) stack.pop(); MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment writeAddress = operand.asSlice(offset()); MemorySegment writeAddress = operand.asSlice(offset());
@ -633,7 +699,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
MemorySegment operand = (MemorySegment) stack.pop(); MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment readAddress = operand.asSlice(offset()); MemorySegment readAddress = operand.asSlice(offset());
stack.push(SharedUtils.read(readAddress, type())); stack.push(SharedUtils.read(readAddress, type()));
@ -673,8 +739,8 @@ public abstract class Binding {
} }
private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment, private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment,
SharedUtils.Allocator allocator) { Context context) {
MemorySegment copy = allocator.allocate(size, alignment); MemorySegment copy = context.allocator().allocate(size, alignment);
copy.copyFrom(operand.asSlice(0, size)); copy.copyFrom(operand.asSlice(0, size));
return copy; return copy;
} }
@ -705,9 +771,9 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
MemorySegment operand = (MemorySegment) stack.pop(); MemorySegment operand = (MemorySegment) stack.pop();
MemorySegment copy = copyBuffer(operand, size, alignment, allocator); MemorySegment copy = copyBuffer(operand, size, alignment, context);
stack.push(copy); stack.push(copy);
} }
@ -715,7 +781,7 @@ public abstract class Binding {
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) { public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, size, alignment); MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, filter); specializedHandle = collectArguments(specializedHandle, insertPos, filter);
return mergeArguments(specializedHandle, allocatorPos, insertPos + 1); return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos + 1);
} }
@Override @Override
@ -748,8 +814,8 @@ public abstract class Binding {
this.alignment = alignment; this.alignment = alignment;
} }
private static MemorySegment allocateBuffer(long size, long allignment, SharedUtils.Allocator allocator) { private static MemorySegment allocateBuffer(long size, long allignment, Context context) {
return allocator.allocate(size, allignment); return context.allocator().allocate(size, allignment);
} }
public long size() { public long size() {
@ -776,15 +842,15 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(allocateBuffer(size, alignment, allocator)); stack.push(allocateBuffer(size, alignment, context));
} }
@Override @Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) { public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle allocateBuffer = insertArguments(MH_ALLOCATE_BUFFER, 0, size, alignment); MethodHandle allocateBuffer = insertArguments(MH_ALLOCATE_BUFFER, 0, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, allocateBuffer); specializedHandle = collectArguments(specializedHandle, insertPos, allocateBuffer);
return mergeArguments(specializedHandle, allocatorPos, insertPos); return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos);
} }
@Override @Override
@ -823,7 +889,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(((MemoryAddress)stack.pop()).toRawLongValue()); stack.push(((MemoryAddress)stack.pop()).toRawLongValue());
} }
@ -839,7 +905,7 @@ public abstract class Binding {
} }
/** /**
* Box_ADDRESS() * BOX_ADDRESS()
* Pops a 'long' from the operand stack, converts it to a 'MemoryAddress', * Pops a 'long' from the operand stack, converts it to a 'MemoryAddress',
* and pushes that onto the operand stack. * and pushes that onto the operand stack.
*/ */
@ -858,7 +924,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(MemoryAddress.ofLong((long) stack.pop())); stack.push(MemoryAddress.ofLong((long) stack.pop()));
} }
@ -893,7 +959,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(((MemorySegment) stack.pop()).address()); stack.push(((MemorySegment) stack.pop()).address());
} }
@ -909,8 +975,8 @@ public abstract class Binding {
} }
/** /**
* BASE_ADDRESS([size]) * TO_SEGMENT([size])
* Pops a MemoryAddress from the operand stack, and takes the converts it to a MemorySegment * Pops a MemoryAddress from the operand stack, and converts it to a MemorySegment
* with the given size, and pushes that onto the operand stack * with the given size, and pushes that onto the operand stack
*/ */
public static class ToSegment extends Binding { public static class ToSegment extends Binding {
@ -922,9 +988,8 @@ public abstract class Binding {
this.size = size; this.size = size;
} }
// FIXME should register with scope private static MemorySegment toSegment(MemoryAddress operand, long size, Context context) {
private static MemorySegment toSegment(MemoryAddress operand, long size) { return MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), size, (ResourceScopeImpl) context.scope);
return MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), size);
} }
@Override @Override
@ -936,16 +1001,17 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
MemoryAddress operand = (MemoryAddress) stack.pop(); MemoryAddress operand = (MemoryAddress) stack.pop();
MemorySegment segment = toSegment(operand, size); MemorySegment segment = toSegment(operand, size, context);
stack.push(segment); stack.push(segment);
} }
@Override @Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) { public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle toSegmentHandle = insertArguments(MH_TO_SEGMENT, 1, size); MethodHandle toSegmentHandle = insertArguments(MH_TO_SEGMENT, 1, size);
return filterArguments(specializedHandle, insertPos, toSegmentHandle); specializedHandle = collectArguments(specializedHandle, insertPos, toSegmentHandle);
return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos + 1);
} }
@Override @Override
@ -988,7 +1054,7 @@ public abstract class Binding {
@Override @Override
public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc, public void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, SharedUtils.Allocator allocator) { BindingInterpreter.LoadFunc loadFunc, Context context) {
stack.push(stack.peekLast()); stack.push(stack.peekLast());
} }
@ -1012,7 +1078,7 @@ public abstract class Binding {
*/ */
@Override @Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) { public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return mergeArguments(specializedHandle, insertPos, insertPos + 1); return SharedUtils.mergeArguments(specializedHandle, insertPos, insertPos + 1);
} }
@Override @Override

View file

@ -30,19 +30,19 @@ import java.util.List;
public class BindingInterpreter { public class BindingInterpreter {
static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, SharedUtils.Allocator allocator) { static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, Binding.Context context) {
Deque<Object> stack = new ArrayDeque<>(); Deque<Object> stack = new ArrayDeque<>();
stack.push(arg); stack.push(arg);
for (Binding b : bindings) { for (Binding b : bindings) {
b.interpret(stack, storeFunc, null, allocator); b.interpret(stack, storeFunc, null, context);
} }
} }
static Object box(List<Binding> bindings, LoadFunc loadFunc, SharedUtils.Allocator allocator) { static Object box(List<Binding> bindings, LoadFunc loadFunc, Binding.Context context) {
Deque<Object> stack = new ArrayDeque<>(); Deque<Object> stack = new ArrayDeque<>();
for (Binding b : bindings) { for (Binding b : bindings) {
b.interpret(stack, null, loadFunc, allocator); b.interpret(stack, null, loadFunc, context);
} }
return stack.pop(); return stack.pop();
} }

View file

@ -26,6 +26,7 @@ package jdk.internal.foreign.abi;
import jdk.incubator.foreign.MemoryLayouts; import jdk.incubator.foreign.MemoryLayouts;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.internal.foreign.MemoryAddressImpl;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
@ -115,7 +116,7 @@ class BufferLayout {
return Long.toHexString((long) VH_LONG.get(buffer.asSlice(offset))); return Long.toHexString((long) VH_LONG.get(buffer.asSlice(offset)));
} }
private static void dumpValues(jdk.internal.foreign.abi.Architecture arch, MemorySegment buff, PrintStream stream, private void dumpValues(jdk.internal.foreign.abi.Architecture arch, MemorySegment buff, PrintStream stream,
Map<jdk.internal.foreign.abi.VMStorage, Long> offsets) { Map<jdk.internal.foreign.abi.VMStorage, Long> offsets) {
for (var entry : offsets.entrySet()) { for (var entry : offsets.entrySet()) {
VMStorage storage = entry.getKey(); VMStorage storage = entry.getKey();
@ -128,6 +129,14 @@ class BufferLayout {
} }
stream.println("}"); stream.println("}");
} }
long stack_ptr = (long) VH_LONG.get(buff.asSlice(stack_args));
long stack_bytes = (long) VH_LONG.get(buff.asSlice(stack_args_bytes));
MemorySegment stackArgs = MemoryAddressImpl.ofLongUnchecked(stack_ptr, stack_bytes);
stream.println("Stack {");
for (int i = 0; i < stack_bytes / 8; i += 8) {
stream.printf(" @%d: %s%n", i, getLongString(stackArgs, i));
}
stream.println("}");
} }
void dump(Architecture arch, MemorySegment buff, PrintStream stream) { void dump(Architecture arch, MemorySegment buff, PrintStream stream) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,12 +25,13 @@
package jdk.internal.foreign.abi; package jdk.internal.foreign.abi;
import jdk.incubator.foreign.Addressable; import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryLayouts; import jdk.incubator.foreign.MemoryLayouts;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.NativeScope; import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets; import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.Utils;
import jdk.internal.invoke.NativeEntryPoint; import jdk.internal.invoke.NativeEntryPoint;
import jdk.internal.invoke.VMStorageProxy; import jdk.internal.invoke.VMStorageProxy;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
@ -39,24 +40,19 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
import java.lang.ref.Reference;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.collectArguments; import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.dropArguments; import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.empty;
import static java.lang.invoke.MethodHandles.filterArguments; import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.identity; import static java.lang.invoke.MethodHandles.identity;
import static java.lang.invoke.MethodHandles.insertArguments; import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.tryFinally;
import static java.lang.invoke.MethodType.methodType; import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.foreign.abi.SharedUtils.Allocator.THROWING_ALLOCATOR;
import static jdk.internal.foreign.abi.SharedUtils.DEFAULT_ALLOCATOR;
import static sun.security.action.GetBooleanAction.privilegedGetProperty; import static sun.security.action.GetBooleanAction.privilegedGetProperty;
/** /**
@ -78,26 +74,27 @@ public class ProgrammableInvoker {
private static final MethodHandle MH_INVOKE_MOVES; private static final MethodHandle MH_INVOKE_MOVES;
private static final MethodHandle MH_INVOKE_INTERP_BINDINGS; private static final MethodHandle MH_INVOKE_INTERP_BINDINGS;
private static final MethodHandle MH_ADDR_TO_LONG;
private static final MethodHandle MH_MAKE_SCOPE; private static final MethodHandle MH_WRAP_ALLOCATOR;
private static final MethodHandle MH_CLOSE_SCOPE;
private static final MethodHandle MH_WRAP_SCOPE;
private static final Map<ABIDescriptor, Long> adapterStubs = new ConcurrentHashMap<>(); private static final Map<ABIDescriptor, Long> adapterStubs = new ConcurrentHashMap<>();
private static final MethodHandle EMPTY_OBJECT_ARRAY_HANDLE = MethodHandles.constant(Object[].class, new Object[0]);
static { static {
try { try {
MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_INVOKE_MOVES = lookup.findVirtual(ProgrammableInvoker.class, "invokeMoves", MH_INVOKE_MOVES = lookup.findVirtual(ProgrammableInvoker.class, "invokeMoves",
methodType(Object.class, Object[].class, Binding.VMStore[].class, Binding.VMLoad[].class)); methodType(Object.class, long.class, Object[].class, Binding.VMStore[].class, Binding.VMLoad[].class));
MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings", MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings",
methodType(Object.class, Object[].class, MethodHandle.class, Map.class, Map.class)); methodType(Object.class, Addressable.class, SegmentAllocator.class, Object[].class, MethodHandle.class, Map.class, Map.class));
MH_MAKE_SCOPE = lookup.findStatic(NativeScope.class, "boundedScope", MH_WRAP_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofAllocator",
methodType(NativeScope.class, long.class)); methodType(Binding.Context.class, SegmentAllocator.class));
MH_CLOSE_SCOPE = lookup.findVirtual(NativeScope.class, "close", MethodHandle MH_Addressable_address = lookup.findVirtual(Addressable.class, "address",
methodType(void.class)); methodType(MemoryAddress.class));
MH_WRAP_SCOPE = lookup.findStatic(SharedUtils.Allocator.class, "ofScope", MethodHandle MH_MemoryAddress_toRawLongValue = lookup.findVirtual(MemoryAddress.class, "toRawLongValue",
methodType(SharedUtils.Allocator.class, NativeScope.class)); methodType(long.class));
MH_ADDR_TO_LONG = filterArguments(MH_MemoryAddress_toRawLongValue, 0, MH_Addressable_address);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -109,17 +106,15 @@ public class ProgrammableInvoker {
private final CallingSequence callingSequence; private final CallingSequence callingSequence;
private final Addressable addr;
private final long stubAddress; private final long stubAddress;
private final long bufferCopySize; private final long bufferCopySize;
public ProgrammableInvoker(ABIDescriptor abi, Addressable addr, CallingSequence callingSequence) { public ProgrammableInvoker(ABIDescriptor abi, CallingSequence callingSequence) {
this.abi = abi; this.abi = abi;
this.layout = BufferLayout.of(abi); this.layout = BufferLayout.of(abi);
this.stubAddress = adapterStubs.computeIfAbsent(abi, key -> generateAdapter(key, layout)); this.stubAddress = adapterStubs.computeIfAbsent(abi, key -> generateAdapter(key, layout));
this.addr = addr;
this.callingSequence = callingSequence; this.callingSequence = callingSequence;
this.stackArgsBytes = argMoveBindingsStream(callingSequence) this.stackArgsBytes = argMoveBindingsStream(callingSequence)
@ -128,24 +123,7 @@ public class ProgrammableInvoker {
.count() .count()
* abi.arch.typeSize(abi.arch.stackType()); * abi.arch.typeSize(abi.arch.stackType());
this.bufferCopySize = bufferCopySize(callingSequence); this.bufferCopySize = SharedUtils.bufferCopySize(callingSequence);
}
private static long bufferCopySize(CallingSequence callingSequence) {
// FIXME: > 16 bytes alignment might need extra space since the
// starting address of the allocator might be un-aligned.
long size = 0;
for (int i = 0; i < callingSequence.argumentCount(); i++) {
List<Binding> bindings = callingSequence.argumentBindings(i);
for (Binding b : bindings) {
if (b instanceof Binding.Copy) {
Binding.Copy c = (Binding.Copy) b;
size = Utils.alignUp(size, c.alignment());
size += c.size();
}
}
}
return size;
} }
public MethodHandle getBoundMethodHandle() { public MethodHandle getBoundMethodHandle() {
@ -160,41 +138,53 @@ public class ProgrammableInvoker {
: Object[].class; : Object[].class;
MethodType leafType = methodType(returnType, argMoveTypes); MethodType leafType = methodType(returnType, argMoveTypes);
MethodType leafTypeWithAddress = leafType.insertParameterTypes(0, long.class);
MethodHandle handle = insertArguments(MH_INVOKE_MOVES.bindTo(this), 1, argMoves, retMoves) MethodHandle handle = insertArguments(MH_INVOKE_MOVES.bindTo(this), 2, argMoves, retMoves);
.asCollector(Object[].class, leafType.parameterCount()) MethodHandle collector = makeCollectorHandle(leafType);
.asType(leafType); handle = collectArguments(handle, 1, collector);
handle = handle.asType(leafTypeWithAddress);
boolean isSimple = !(retMoves.length > 1); boolean isSimple = !(retMoves.length > 1);
boolean usesStackArgs = stackArgsBytes != 0; boolean usesStackArgs = stackArgsBytes != 0;
if (USE_INTRINSICS && isSimple && !usesStackArgs) { if (USE_INTRINSICS && isSimple && !usesStackArgs) {
NativeEntryPoint nep = NativeEntryPoint.make( NativeEntryPoint nep = NativeEntryPoint.make(
addr.address().toRawLongValue(),
"native_call", "native_call",
abi, abi,
toStorageArray(argMoves), toStorageArray(argMoves),
toStorageArray(retMoves), toStorageArray(retMoves),
!callingSequence.isTrivial(), !callingSequence.isTrivial(),
leafType leafTypeWithAddress
); );
handle = JLIA.nativeMethodHandle(nep, handle); handle = JLIA.nativeMethodHandle(nep, handle);
} }
handle = filterArguments(handle, 0, MH_ADDR_TO_LONG);
if (USE_SPEC && isSimple) { if (USE_SPEC && isSimple) {
handle = specialize(handle); handle = specialize(handle);
} else { } else {
Map<VMStorage, Integer> argIndexMap = indexMap(argMoves); Map<VMStorage, Integer> argIndexMap = SharedUtils.indexMap(argMoves);
Map<VMStorage, Integer> retIndexMap = indexMap(retMoves); Map<VMStorage, Integer> retIndexMap = SharedUtils.indexMap(retMoves);
handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 1, handle, argIndexMap, retIndexMap); handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 3, handle, argIndexMap, retIndexMap);
handle = handle.asCollector(Object[].class, callingSequence.methodType().parameterCount()) MethodHandle collectorInterp = makeCollectorHandle(callingSequence.methodType());
.asType(callingSequence.methodType()); handle = collectArguments(handle, 2, collectorInterp);
handle = handle.asType(handle.type().changeReturnType(callingSequence.methodType().returnType()));
} }
return handle; return handle;
} }
// Funnel from type to Object[]
private static MethodHandle makeCollectorHandle(MethodType type) {
return type.parameterCount() == 0
? EMPTY_OBJECT_ARRAY_HANDLE
: identity(Object[].class)
.asCollector(Object[].class, type.parameterCount())
.asType(type.changeReturnType(Object[].class));
}
private Stream<Binding.VMStore> argMoveBindingsStream(CallingSequence callingSequence) { private Stream<Binding.VMStore> argMoveBindingsStream(CallingSequence callingSequence) {
return callingSequence.argumentBindings() return callingSequence.argumentBindings()
.filter(Binding.VMStore.class::isInstance) .filter(Binding.VMStore.class::isInstance)
@ -215,17 +205,12 @@ public class ProgrammableInvoker {
private MethodHandle specialize(MethodHandle leafHandle) { private MethodHandle specialize(MethodHandle leafHandle) {
MethodType highLevelType = callingSequence.methodType(); MethodType highLevelType = callingSequence.methodType();
MethodType leafType = leafHandle.type();
MethodHandle specializedHandle = leafHandle; // initial int argInsertPos = 1;
int argContextPos = 1;
MethodHandle specializedHandle = dropArguments(leafHandle, argContextPos, Binding.Context.class);
int argInsertPos = -1;
int argAllocatorPos = -1;
if (bufferCopySize > 0) {
argAllocatorPos = 0;
specializedHandle = dropArguments(specializedHandle, argAllocatorPos, SharedUtils.Allocator.class);
argInsertPos++;
}
for (int i = 0; i < highLevelType.parameterCount(); i++) { for (int i = 0; i < highLevelType.parameterCount(); i++) {
List<Binding> bindings = callingSequence.argumentBindings(i); List<Binding> bindings = callingSequence.argumentBindings(i);
argInsertPos += bindings.stream().filter(Binding.VMStore.class::isInstance).count() + 1; argInsertPos += bindings.stream().filter(Binding.VMStore.class::isInstance).count() + 1;
@ -235,48 +220,37 @@ public class ProgrammableInvoker {
if (binding.tag() == Binding.Tag.VM_STORE) { if (binding.tag() == Binding.Tag.VM_STORE) {
argInsertPos--; argInsertPos--;
} else { } else {
specializedHandle = binding.specialize(specializedHandle, argInsertPos, argAllocatorPos); specializedHandle = binding.specialize(specializedHandle, argInsertPos, argContextPos);
} }
} }
} }
if (highLevelType.returnType() != void.class) { if (highLevelType.returnType() != void.class) {
MethodHandle returnFilter = identity(highLevelType.returnType()); MethodHandle returnFilter = identity(highLevelType.returnType());
int retAllocatorPos = 0; int retContextPos = 0;
int retInsertPos = 1; int retInsertPos = 1;
returnFilter = dropArguments(returnFilter, retAllocatorPos, SharedUtils.Allocator.class); returnFilter = dropArguments(returnFilter, retContextPos, Binding.Context.class);
List<Binding> bindings = callingSequence.returnBindings(); List<Binding> bindings = callingSequence.returnBindings();
for (int j = bindings.size() - 1; j >= 0; j--) { for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j); Binding binding = bindings.get(j);
returnFilter = binding.specialize(returnFilter, retInsertPos, retAllocatorPos); returnFilter = binding.specialize(returnFilter, retInsertPos, retContextPos);
} }
returnFilter = insertArguments(returnFilter, retAllocatorPos, DEFAULT_ALLOCATOR); returnFilter = MethodHandles.filterArguments(returnFilter, retContextPos, MH_WRAP_ALLOCATOR);
specializedHandle = MethodHandles.filterReturnValue(specializedHandle, returnFilter); // (SegmentAllocator, Addressable, Context, ...) -> ...
specializedHandle = MethodHandles.collectArguments(returnFilter, retInsertPos, specializedHandle);
// (Addressable, SegmentAllocator, Context, ...) -> ...
specializedHandle = SharedUtils.swapArguments(specializedHandle, 0, 1); // normalize parameter order
} else {
specializedHandle = MethodHandles.dropArguments(specializedHandle, 1, SegmentAllocator.class);
} }
if (bufferCopySize > 0) { // now bind the internal context parameter
// insert try-finally to close the NativeScope used for Binding.Copy
MethodHandle closer = leafType.returnType() == void.class argContextPos++; // skip over the return SegmentAllocator (inserted by the above code)
// (Throwable, NativeScope) -> void specializedHandle = SharedUtils.wrapWithAllocator(specializedHandle, argContextPos, bufferCopySize, false);
? collectArguments(empty(methodType(void.class, Throwable.class)), 1, MH_CLOSE_SCOPE)
// (Throwable, V, NativeScope) -> V
: collectArguments(dropArguments(identity(specializedHandle.type().returnType()), 0, Throwable.class),
2, MH_CLOSE_SCOPE);
// Handle takes a SharedUtils.Allocator, so need to wrap our NativeScope
specializedHandle = filterArguments(specializedHandle, argAllocatorPos, MH_WRAP_SCOPE);
specializedHandle = tryFinally(specializedHandle, closer);
MethodHandle makeScopeHandle = insertArguments(MH_MAKE_SCOPE, 0, bufferCopySize);
specializedHandle = collectArguments(specializedHandle, argAllocatorPos, makeScopeHandle);
}
return specializedHandle; return specializedHandle;
} }
private static Map<VMStorage, Integer> indexMap(Binding.Move[] moves) {
return IntStream.range(0, moves.length)
.boxed()
.collect(Collectors.toMap(i -> moves[i].storage(), i -> i));
}
/** /**
* Does a native invocation by moving primitive values from the arg array into an intermediate buffer * Does a native invocation by moving primitive values from the arg array into an intermediate buffer
* and calling the assembly stub that forwards arguments from the buffer to the target function * and calling the assembly stub that forwards arguments from the buffer to the target function
@ -286,14 +260,15 @@ public class ProgrammableInvoker {
* @param returnBindings Binding.Move values describing how return values should be copied * @param returnBindings Binding.Move values describing how return values should be copied
* @return null, a single primitive value, or an Object[] of primitive values * @return null, a single primitive value, or an Object[] of primitive values
*/ */
Object invokeMoves(Object[] args, Binding.VMStore[] argBindings, Binding.VMLoad[] returnBindings) { Object invokeMoves(long addr, Object[] args, Binding.VMStore[] argBindings, Binding.VMLoad[] returnBindings) {
MemorySegment stackArgsSeg = null; MemorySegment stackArgsSeg = null;
try (MemorySegment argBuffer = MemorySegment.allocateNative(layout.size, 64)) { try (ResourceScope scope = ResourceScope.newConfinedScope()) {
MemorySegment argBuffer = MemorySegment.allocateNative(layout.size, 64, scope);
if (stackArgsBytes > 0) { if (stackArgsBytes > 0) {
stackArgsSeg = MemorySegment.allocateNative(stackArgsBytes, 8); stackArgsSeg = MemorySegment.allocateNative(stackArgsBytes, 8, scope);
} }
VH_LONG.set(argBuffer.asSlice(layout.arguments_next_pc), addr.address().toRawLongValue()); VH_LONG.set(argBuffer.asSlice(layout.arguments_next_pc), addr);
VH_LONG.set(argBuffer.asSlice(layout.stack_args_bytes), stackArgsBytes); VH_LONG.set(argBuffer.asSlice(layout.stack_args_bytes), stackArgsBytes);
VH_LONG.set(argBuffer.asSlice(layout.stack_args), stackArgsSeg == null ? 0L : stackArgsSeg.address().toRawLongValue()); VH_LONG.set(argBuffer.asSlice(layout.stack_args), stackArgsSeg == null ? 0L : stackArgsSeg.address().toRawLongValue());
@ -333,32 +308,33 @@ public class ProgrammableInvoker {
} }
return returns; return returns;
} }
} finally {
if (stackArgsSeg != null) {
stackArgsSeg.close();
}
} }
} }
Object invokeInterpBindings(Object[] args, MethodHandle leaf, Object invokeInterpBindings(Addressable address, SegmentAllocator allocator, Object[] args, MethodHandle leaf,
Map<VMStorage, Integer> argIndexMap, Map<VMStorage, Integer> argIndexMap,
Map<VMStorage, Integer> retIndexMap) throws Throwable { Map<VMStorage, Integer> retIndexMap) throws Throwable {
SharedUtils.Allocator unboxAllocator = bufferCopySize != 0 Binding.Context unboxContext = bufferCopySize != 0
? SharedUtils.Allocator.ofScope(NativeScope.boundedScope(bufferCopySize)) ? Binding.Context.ofBoundedAllocator(bufferCopySize)
: THROWING_ALLOCATOR; : Binding.Context.DUMMY;
try (unboxAllocator) { try (unboxContext) {
// do argument processing, get Object[] as result // do argument processing, get Object[] as result
Object[] moves = new Object[leaf.type().parameterCount()]; Object[] leafArgs = new Object[leaf.type().parameterCount()];
leafArgs[0] = address; // addr
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
Object arg = args[i]; Object arg = args[i];
BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i), BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i),
(storage, type, value) -> { (storage, type, value) -> {
moves[argIndexMap.get(storage)] = value; leafArgs[argIndexMap.get(storage) + 1] = value; // +1 to skip addr
}, unboxAllocator); }, unboxContext);
} }
// call leaf // call leaf
Object o = leaf.invokeWithArguments(moves); Object o = leaf.invokeWithArguments(leafArgs);
// make sure arguments are reachable during the call
// technically we only need to do all Addressable parameters here
Reference.reachabilityFence(address);
Reference.reachabilityFence(args);
// return value processing // return value processing
if (o == null) { if (o == null) {
@ -366,10 +342,10 @@ public class ProgrammableInvoker {
} else if (o instanceof Object[]) { } else if (o instanceof Object[]) {
Object[] oArr = (Object[]) o; Object[] oArr = (Object[]) o;
return BindingInterpreter.box(callingSequence.returnBindings(), return BindingInterpreter.box(callingSequence.returnBindings(),
(storage, type) -> oArr[retIndexMap.get(storage)], DEFAULT_ALLOCATOR); (storage, type) -> oArr[retIndexMap.get(storage)], Binding.Context.ofAllocator(allocator));
} else { } else {
return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o, return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o,
DEFAULT_ALLOCATOR); Binding.Context.ofAllocator(allocator));
} }
} }
} }

View file

@ -26,22 +26,32 @@
package jdk.internal.foreign.abi; package jdk.internal.foreign.abi;
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayouts; import jdk.incubator.foreign.MemoryLayouts;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.MemoryAddressImpl; import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.Utils; import sun.security.action.GetPropertyAction;
import jdk.internal.vm.annotation.Stable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
import static jdk.internal.foreign.abi.SharedUtils.DEFAULT_ALLOCATOR; import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.filterReturnValue;
import static java.lang.invoke.MethodHandles.identity;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.foreign.abi.SharedUtils.mergeArguments;
import static sun.security.action.GetBooleanAction.privilegedGetProperty; import static sun.security.action.GetBooleanAction.privilegedGetProperty;
/** /**
@ -49,42 +59,157 @@ import static sun.security.action.GetBooleanAction.privilegedGetProperty;
* takes an array of storage pointers, which describes the state of the CPU at the time of the upcall. This can be used * takes an array of storage pointers, which describes the state of the CPU at the time of the upcall. This can be used
* by the Java code to fetch the upcall arguments and to store the results to the desired location, as per system ABI. * by the Java code to fetch the upcall arguments and to store the results to the desired location, as per system ABI.
*/ */
public class ProgrammableUpcallHandler implements UpcallHandler { public class ProgrammableUpcallHandler {
private static final boolean DEBUG = private static final boolean DEBUG =
privilegedGetProperty("jdk.internal.foreign.ProgrammableUpcallHandler.DEBUG"); privilegedGetProperty("jdk.internal.foreign.ProgrammableUpcallHandler.DEBUG");
private static final boolean USE_SPEC = Boolean.parseBoolean(
GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.ProgrammableUpcallHandler.USE_SPEC", "true"));
private static final boolean USE_INTRINSICS = Boolean.parseBoolean(
GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.ProgrammableUpcallHandler.USE_INTRINSICS", "true"));
private static final JavaLangInvokeAccess JLI = SharedSecrets.getJavaLangInvokeAccess();
private static final VarHandle VH_LONG = MemoryLayouts.JAVA_LONG.varHandle(long.class); private static final VarHandle VH_LONG = MemoryLayouts.JAVA_LONG.varHandle(long.class);
@Stable private static final MethodHandle MH_invokeMoves;
private final MethodHandle mh; private static final MethodHandle MH_invokeInterpBindings;
private final MethodType type;
private final CallingSequence callingSequence;
private final long entryPoint;
private final ABIDescriptor abi; static {
private final BufferLayout layout;
public ProgrammableUpcallHandler(ABIDescriptor abi, MethodHandle target, CallingSequence callingSequence) {
this.abi = abi;
this.layout = BufferLayout.of(abi);
this.type = callingSequence.methodType();
this.callingSequence = callingSequence;
this.mh = target.asSpreader(Object[].class, callingSequence.methodType().parameterCount());
this.entryPoint = allocateUpcallStub(abi, layout);
}
@Override
public long entryPoint() {
return entryPoint;
}
public static void invoke(ProgrammableUpcallHandler handler, long address) {
handler.invoke(MemoryAddress.ofLong(address));
}
private void invoke(MemoryAddress buffer) {
try { try {
MethodHandles.Lookup lookup = lookup();
MH_invokeMoves = lookup.findStatic(ProgrammableUpcallHandler.class, "invokeMoves",
methodType(void.class, MemoryAddress.class, MethodHandle.class,
Binding.VMLoad[].class, Binding.VMStore[].class, ABIDescriptor.class, BufferLayout.class));
MH_invokeInterpBindings = lookup.findStatic(ProgrammableUpcallHandler.class, "invokeInterpBindings",
methodType(Object.class, Object[].class, MethodHandle.class, Map.class, Map.class,
CallingSequence.class, long.class));
} catch (ReflectiveOperationException e) {
throw new InternalError(e);
}
}
public static UpcallHandler make(ABIDescriptor abi, MethodHandle target, CallingSequence callingSequence) {
Binding.VMLoad[] argMoves = argMoveBindings(callingSequence);
Binding.VMStore[] retMoves = retMoveBindings(callingSequence);
boolean isSimple = !(retMoves.length > 1);
Class<?> llReturn = !isSimple
? Object[].class
: retMoves.length == 1
? retMoves[0].type()
: void.class;
Class<?>[] llParams = Arrays.stream(argMoves).map(Binding.Move::type).toArray(Class<?>[]::new);
MethodType llType = MethodType.methodType(llReturn, llParams);
MethodHandle doBindings;
long bufferCopySize = SharedUtils.bufferCopySize(callingSequence);
if (USE_SPEC && isSimple) {
doBindings = specializedBindingHandle(target, callingSequence, llReturn, bufferCopySize);
assert doBindings.type() == llType;
} else {
Map<VMStorage, Integer> argIndices = SharedUtils.indexMap(argMoves);
Map<VMStorage, Integer> retIndices = SharedUtils.indexMap(retMoves);
target = target.asSpreader(Object[].class, callingSequence.methodType().parameterCount());
doBindings = insertArguments(MH_invokeInterpBindings, 1, target, argIndices, retIndices, callingSequence,
bufferCopySize);
doBindings = doBindings.asCollector(Object[].class, llType.parameterCount());
doBindings = doBindings.asType(llType);
}
long entryPoint;
boolean usesStackArgs = argMoveBindingsStream(callingSequence)
.map(Binding.VMLoad::storage)
.anyMatch(s -> abi.arch.isStackType(s.type()));
if (USE_INTRINSICS && isSimple && !usesStackArgs && supportsOptimizedUpcalls()) {
checkPrimitive(doBindings.type());
JLI.ensureCustomized(doBindings);
VMStorage[] args = Arrays.stream(argMoves).map(Binding.Move::storage).toArray(VMStorage[]::new);
VMStorage[] rets = Arrays.stream(retMoves).map(Binding.Move::storage).toArray(VMStorage[]::new);
CallRegs conv = new CallRegs(args, rets);
entryPoint = allocateOptimizedUpcallStub(doBindings, abi, conv);
} else {
BufferLayout layout = BufferLayout.of(abi);
MethodHandle doBindingsErased = doBindings.asSpreader(Object[].class, doBindings.type().parameterCount());
MethodHandle invokeMoves = insertArguments(MH_invokeMoves, 1, doBindingsErased, argMoves, retMoves, abi, layout);
entryPoint = allocateUpcallStub(invokeMoves, abi, layout);
}
return () -> entryPoint;
}
private static void checkPrimitive(MethodType type) {
if (!type.returnType().isPrimitive()
|| type.parameterList().stream().anyMatch(p -> !p.isPrimitive()))
throw new IllegalArgumentException("MethodHandle type must be primitive: " + type);
}
private static Stream<Binding.VMLoad> argMoveBindingsStream(CallingSequence callingSequence) {
return callingSequence.argumentBindings()
.filter(Binding.VMLoad.class::isInstance)
.map(Binding.VMLoad.class::cast);
}
private static Binding.VMLoad[] argMoveBindings(CallingSequence callingSequence) {
return argMoveBindingsStream(callingSequence)
.toArray(Binding.VMLoad[]::new);
}
private static Binding.VMStore[] retMoveBindings(CallingSequence callingSequence) {
return callingSequence.returnBindings().stream()
.filter(Binding.VMStore.class::isInstance)
.map(Binding.VMStore.class::cast)
.toArray(Binding.VMStore[]::new);
}
private static MethodHandle specializedBindingHandle(MethodHandle target, CallingSequence callingSequence,
Class<?> llReturn, long bufferCopySize) {
MethodType highLevelType = callingSequence.methodType();
MethodHandle specializedHandle = target; // initial
int argAllocatorPos = 0;
int argInsertPos = 1;
specializedHandle = dropArguments(specializedHandle, argAllocatorPos, Binding.Context.class);
for (int i = 0; i < highLevelType.parameterCount(); i++) {
MethodHandle filter = identity(highLevelType.parameterType(i));
int filterAllocatorPos = 0;
int filterInsertPos = 1; // +1 for allocator
filter = dropArguments(filter, filterAllocatorPos, Binding.Context.class);
List<Binding> bindings = callingSequence.argumentBindings(i);
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
filter = binding.specialize(filter, filterInsertPos, filterAllocatorPos);
}
specializedHandle = MethodHandles.collectArguments(specializedHandle, argInsertPos, filter);
specializedHandle = mergeArguments(specializedHandle, argAllocatorPos, argInsertPos + filterAllocatorPos);
argInsertPos += filter.type().parameterCount() - 1; // -1 for allocator
}
if (llReturn != void.class) {
int retAllocatorPos = -1; // assumed not needed
int retInsertPos = 0;
MethodHandle filter = identity(llReturn);
List<Binding> bindings = callingSequence.returnBindings();
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
filter = binding.specialize(filter, retInsertPos, retAllocatorPos);
}
specializedHandle = filterReturnValue(specializedHandle, filter);
}
specializedHandle = SharedUtils.wrapWithAllocator(specializedHandle, argAllocatorPos, bufferCopySize, true);
return specializedHandle;
}
public static void invoke(MethodHandle mh, long address) throws Throwable {
mh.invokeExact(MemoryAddress.ofLong(address));
}
private static void invokeMoves(MemoryAddress buffer, MethodHandle leaf,
Binding.VMLoad[] argBindings, Binding.VMStore[] returnBindings,
ABIDescriptor abi, BufferLayout layout) throws Throwable {
MemorySegment bufferBase = MemoryAddressImpl.ofLongUnchecked(buffer.toRawLongValue(), layout.size); MemorySegment bufferBase = MemoryAddressImpl.ofLongUnchecked(buffer.toRawLongValue(), layout.size);
if (DEBUG) { if (DEBUG) {
@ -93,15 +218,55 @@ public class ProgrammableUpcallHandler implements UpcallHandler {
} }
MemorySegment stackArgsBase = MemoryAddressImpl.ofLongUnchecked((long)VH_LONG.get(bufferBase.asSlice(layout.stack_args))); MemorySegment stackArgsBase = MemoryAddressImpl.ofLongUnchecked((long)VH_LONG.get(bufferBase.asSlice(layout.stack_args)));
Object[] args = new Object[type.parameterCount()]; Object[] moves = new Object[argBindings.length];
for (int i = 0 ; i < type.parameterCount() ; i++) { for (int i = 0; i < moves.length; i++) {
args[i] = BindingInterpreter.box(callingSequence.argumentBindings(i), Binding.VMLoad binding = argBindings[i];
(storage, type) -> { VMStorage storage = binding.storage();
MemorySegment ptr = abi.arch.isStackType(storage.type()) MemorySegment ptr = abi.arch.isStackType(storage.type())
? stackArgsBase.asSlice(storage.index() * abi.arch.typeSize(abi.arch.stackType())) ? stackArgsBase.asSlice(storage.index() * abi.arch.typeSize(abi.arch.stackType()))
: bufferBase.asSlice(layout.argOffset(storage)); : bufferBase.asSlice(layout.argOffset(storage));
return SharedUtils.read(ptr, type); moves[i] = SharedUtils.read(ptr, binding.type());
}, DEFAULT_ALLOCATOR); }
// invokeInterpBindings, and then actual target
Object o = leaf.invoke(moves);
if (o == null) {
// nop
} else if (o instanceof Object[] returns) {
for (int i = 0; i < returnBindings.length; i++) {
Binding.VMStore binding = returnBindings[i];
VMStorage storage = binding.storage();
MemorySegment ptr = bufferBase.asSlice(layout.retOffset(storage));
SharedUtils.writeOverSized(ptr, binding.type(), returns[i]);
}
} else { // single Object
Binding.VMStore binding = returnBindings[0];
VMStorage storage = binding.storage();
MemorySegment ptr = bufferBase.asSlice(layout.retOffset(storage));
SharedUtils.writeOverSized(ptr, binding.type(), o);
}
if (DEBUG) {
System.err.println("Buffer state after:");
layout.dump(abi.arch, bufferBase, System.err);
}
}
private static Object invokeInterpBindings(Object[] moves, MethodHandle leaf,
Map<VMStorage, Integer> argIndexMap,
Map<VMStorage, Integer> retIndexMap,
CallingSequence callingSequence,
long bufferCopySize) throws Throwable {
Binding.Context allocator = bufferCopySize != 0
? Binding.Context.ofBoundedAllocator(bufferCopySize)
: Binding.Context.ofScope();
try (allocator) {
/// Invoke interpreter, got array of high-level arguments back
Object[] args = new Object[callingSequence.methodType().parameterCount()];
for (int i = 0; i < args.length; i++) {
args[i] = BindingInterpreter.box(callingSequence.argumentBindings(i),
(storage, type) -> moves[argIndexMap.get(storage)], allocator);
} }
if (DEBUG) { if (DEBUG) {
@ -109,31 +274,36 @@ public class ProgrammableUpcallHandler implements UpcallHandler {
System.err.println(Arrays.toString(args).indent(2)); System.err.println(Arrays.toString(args).indent(2));
} }
Object o = mh.invoke(args); // invoke our target
Object o = leaf.invoke(args);
if (DEBUG) { if (DEBUG) {
System.err.println("Java return:"); System.err.println("Java return:");
System.err.println(Objects.toString(o).indent(2)); System.err.println(Objects.toString(o).indent(2));
} }
if (mh.type().returnType() != void.class) { Object[] returnMoves = new Object[retIndexMap.size()];
if (leaf.type().returnType() != void.class) {
BindingInterpreter.unbox(o, callingSequence.returnBindings(), BindingInterpreter.unbox(o, callingSequence.returnBindings(),
(storage, type, value) -> { (storage, type, value) -> returnMoves[retIndexMap.get(storage)] = value, null);
MemorySegment ptr = bufferBase.asSlice(layout.retOffset(storage));
SharedUtils.writeOverSized(ptr, type, value);
}, null);
} }
if (DEBUG) { if (returnMoves.length == 0) {
System.err.println("Buffer state after:"); return null;
layout.dump(abi.arch, bufferBase, System.err); } else if (returnMoves.length == 1) {
return returnMoves[0];
} else {
return returnMoves;
} }
} catch (Throwable t) {
throw new IllegalStateException(t);
} }
} }
public native long allocateUpcallStub(ABIDescriptor abi, BufferLayout layout); // used for transporting data into native code
private static record CallRegs(VMStorage[] argRegs, VMStorage[] retRegs) {}
static native long allocateOptimizedUpcallStub(MethodHandle mh, ABIDescriptor abi, CallRegs conv);
static native long allocateUpcallStub(MethodHandle mh, ABIDescriptor abi, BufferLayout layout);
static native boolean supportsOptimizedUpcalls();
private static native void registerNatives(); private static native void registerNatives();
static { static {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,7 @@
*/ */
package jdk.internal.foreign.abi; package jdk.internal.foreign.abi;
import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.FunctionDescriptor; import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.GroupLayout; import jdk.incubator.foreign.GroupLayout;
import jdk.incubator.foreign.MemoryAccess; import jdk.incubator.foreign.MemoryAccess;
@ -32,7 +33,8 @@ import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.LibraryLookup; import jdk.incubator.foreign.LibraryLookup;
import jdk.incubator.foreign.NativeScope; import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.incubator.foreign.SequenceLayout; import jdk.incubator.foreign.SequenceLayout;
import jdk.incubator.foreign.CLinker; import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.ValueLayout; import jdk.incubator.foreign.ValueLayout;
@ -47,15 +49,24 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
import java.lang.ref.Reference;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import static java.lang.invoke.MethodHandles.collectArguments; import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.constant;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.dropReturn;
import static java.lang.invoke.MethodHandles.empty;
import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.identity; import static java.lang.invoke.MethodHandles.identity;
import static java.lang.invoke.MethodHandles.insertArguments; import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.permuteArguments; import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.invoke.MethodHandles.tryFinally;
import static java.lang.invoke.MethodType.methodType; import static java.lang.invoke.MethodType.methodType;
import static jdk.incubator.foreign.CLinker.*; import static jdk.incubator.foreign.CLinker.*;
@ -64,27 +75,35 @@ public class SharedUtils {
private static final MethodHandle MH_ALLOC_BUFFER; private static final MethodHandle MH_ALLOC_BUFFER;
private static final MethodHandle MH_BASEADDRESS; private static final MethodHandle MH_BASEADDRESS;
private static final MethodHandle MH_BUFFER_COPY; private static final MethodHandle MH_BUFFER_COPY;
private static final MethodHandle MH_MAKE_CONTEXT_NO_ALLOCATOR;
static final Allocator DEFAULT_ALLOCATOR = MemorySegment::allocateNative; private static final MethodHandle MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR;
private static final MethodHandle MH_CLOSE_CONTEXT;
private static final MethodHandle MH_REACHBILITY_FENCE;
static { static {
try { try {
var lookup = MethodHandles.lookup(); MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_ALLOC_BUFFER = lookup.findStatic(SharedUtils.class, "allocateNative", MH_ALLOC_BUFFER = lookup.findVirtual(SegmentAllocator.class, "allocate",
methodType(MemorySegment.class, MemoryLayout.class)); methodType(MemorySegment.class, MemoryLayout.class));
MH_BASEADDRESS = lookup.findVirtual(MemorySegment.class, "address", MH_BASEADDRESS = lookup.findVirtual(MemorySegment.class, "address",
methodType(MemoryAddress.class)); methodType(MemoryAddress.class));
MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy", MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy",
methodType(MemoryAddress.class, MemoryAddress.class, MemorySegment.class)); methodType(MemoryAddress.class, MemoryAddress.class, MemorySegment.class));
MH_MAKE_CONTEXT_NO_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofScope",
methodType(Binding.Context.class));
MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofBoundedAllocator",
methodType(Binding.Context.class, long.class));
MH_CLOSE_CONTEXT = lookup.findVirtual(Binding.Context.class, "close",
methodType(void.class));
MH_REACHBILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence",
methodType(void.class, Object.class));
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new BootstrapMethodError(e); throw new BootstrapMethodError(e);
} }
} }
// workaround for https://bugs.openjdk.java.net/browse/JDK-8239083 // this allocator should be used when no allocation is expected
private static MemorySegment allocateNative(MemoryLayout layout) { public static final SegmentAllocator THROWING_ALLOCATOR = (size, align) -> { throw new IllegalStateException("Cannot get here"); };
return MemorySegment.allocateNative(layout);
}
/** /**
* Align the specified type from a given address * Align the specified type from a given address
@ -154,22 +173,19 @@ public class SharedUtils {
*/ */
public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc) { public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc) {
if (handle.type().returnType() != void.class) if (handle.type().returnType() != void.class)
throw new IllegalArgumentException("return expected to be void for in memory returns"); throw new IllegalArgumentException("return expected to be void for in memory returns: " + handle.type());
if (handle.type().parameterType(0) != MemoryAddress.class) if (handle.type().parameterType(2) != MemoryAddress.class)
throw new IllegalArgumentException("MemoryAddress expected as first param"); throw new IllegalArgumentException("MemoryAddress expected as third param: " + handle.type());
if (cDesc.returnLayout().isEmpty()) if (cDesc.returnLayout().isEmpty())
throw new IllegalArgumentException("Return layout needed: " + cDesc); throw new IllegalArgumentException("Return layout needed: " + cDesc);
MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment
handle = collectArguments(ret, 1, handle); // (MemorySegment, MemoryAddress ...) MemorySegment handle = collectArguments(ret, 1, handle); // (MemorySegment, Addressable, SegmentAllocator, MemoryAddress, ...) MemorySegment
handle = collectArguments(handle, 1, MH_BASEADDRESS); // (MemorySegment, MemorySegment ...) MemorySegment handle = collectArguments(handle, 3, MH_BASEADDRESS); // (MemorySegment, Addressable, SegmentAllocator, MemorySegment, ...) MemorySegment
MethodType oldType = handle.type(); // (MemorySegment, MemorySegment, ...) MemorySegment handle = mergeArguments(handle, 0, 3); // (MemorySegment, Addressable, SegmentAllocator, ...) MemorySegment
MethodType newType = oldType.dropParameterTypes(0, 1); // (MemorySegment, ...) MemorySegment handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 1, cDesc.returnLayout().get())); // (SegmentAllocator, Addressable, SegmentAllocator, ...) MemoryAddress
int[] reorder = IntStream.range(-1, newType.parameterCount()).toArray(); handle = mergeArguments(handle, 0, 2); // (SegmentAllocator, Addressable, ...) MemoryAddress
reorder[0] = 0; // [0, 0, 1, 2, 3, ...] handle = swapArguments(handle, 0, 1); // (Addressable, SegmentAllocator, ...) MemoryAddress
handle = permuteArguments(handle, newType, reorder); // (MemorySegment, ...) MemoryAddress
handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 0, cDesc.returnLayout().get())); // (...) MemoryAddress
return handle; return handle;
} }
@ -181,12 +197,16 @@ public class SharedUtils {
* @param target the target handle to adapt * @param target the target handle to adapt
* @return the adapted handle * @return the adapted handle
*/ */
public static MethodHandle adaptUpcallForIMR(MethodHandle target) { public static MethodHandle adaptUpcallForIMR(MethodHandle target, boolean dropReturn) {
if (target.type().returnType() != MemorySegment.class) if (target.type().returnType() != MemorySegment.class)
throw new IllegalArgumentException("Must return MemorySegment for IMR"); throw new IllegalArgumentException("Must return MemorySegment for IMR");
target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemoryAddress, ...) MemoryAddress target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemoryAddress, ...) MemoryAddress
if (dropReturn) { // no handling for return value, need to drop it
target = dropReturn(target);
}
return target; return target;
} }
@ -223,7 +243,14 @@ public class SharedUtils {
cDesc.returnLayout().ifPresent(rl -> checkCompatibleType(mt.returnType(), rl, addressSize)); cDesc.returnLayout().ifPresent(rl -> checkCompatibleType(mt.returnType(), rl, addressSize));
} }
public static Class<?> primitiveCarrierForSize(long size) { public static Class<?> primitiveCarrierForSize(long size, boolean useFloat) {
if (useFloat) {
if (size == 4) {
return float.class;
} else if (size == 8) {
return double.class;
}
} else {
if (size == 1) { if (size == 1) {
return byte.class; return byte.class;
} else if (size == 2) { } else if (size == 2) {
@ -233,8 +260,9 @@ public class SharedUtils {
} else if (size <= 8) { } else if (size <= 8) {
return long.class; return long.class;
} }
}
throw new IllegalArgumentException("Size too large: " + size); throw new IllegalArgumentException("No type for size: " + size + " isFloat=" + useFloat);
} }
public static CLinker getSystemLinker() { public static CLinker getSystemLinker() {
@ -264,16 +292,136 @@ public class SharedUtils {
throw new IllegalArgumentException("String too large"); throw new IllegalArgumentException("String too large");
} }
static long bufferCopySize(CallingSequence callingSequence) {
// FIXME: > 16 bytes alignment might need extra space since the
// starting address of the allocator might be un-aligned.
long size = 0;
for (int i = 0; i < callingSequence.argumentCount(); i++) {
List<Binding> bindings = callingSequence.argumentBindings(i);
for (Binding b : bindings) {
if (b instanceof Binding.Copy) {
Binding.Copy c = (Binding.Copy) b;
size = Utils.alignUp(size, c.alignment());
size += c.size();
} else if (b instanceof Binding.Allocate) {
Binding.Allocate c = (Binding.Allocate) b;
size = Utils.alignUp(size, c.alignment());
size += c.size();
}
}
}
return size;
}
static Map<VMStorage, Integer> indexMap(Binding.Move[] moves) {
return IntStream.range(0, moves.length)
.boxed()
.collect(Collectors.toMap(i -> moves[i].storage(), i -> i));
}
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);
}
static MethodHandle swapArguments(MethodHandle mh, int firstArg, int secondArg) {
MethodType mtype = mh.type();
int[] perms = new int[mtype.parameterCount()];
MethodType swappedType = MethodType.methodType(mtype.returnType());
for (int i = 0 ; i < perms.length ; i++) {
int dst = i;
if (i == firstArg) dst = secondArg;
if (i == secondArg) dst = firstArg;
perms[i] = dst;
swappedType = swappedType.appendParameterTypes(mtype.parameterType(dst));
}
return permuteArguments(mh, swappedType, perms);
}
private static MethodHandle reachabilityFenceHandle(Class<?> type) {
return MH_REACHBILITY_FENCE.asType(MethodType.methodType(void.class, type));
}
static MethodHandle wrapWithAllocator(MethodHandle specializedHandle,
int allocatorPos, long bufferCopySize,
boolean upcall) {
// insert try-finally to close the NativeScope used for Binding.Copy
MethodHandle closer;
int insertPos;
if (specializedHandle.type().returnType() == void.class) {
closer = empty(methodType(void.class, Throwable.class)); // (Throwable) -> void
insertPos = 1;
} else {
closer = identity(specializedHandle.type().returnType()); // (V) -> V
closer = dropArguments(closer, 0, Throwable.class); // (Throwable, V) -> V
insertPos = 2;
}
// downcalls get the leading Addressable/SegmentAllocator param as well
if (!upcall) {
closer = collectArguments(closer, insertPos++, reachabilityFenceHandle(Addressable.class));
closer = dropArguments(closer, insertPos++, SegmentAllocator.class); // (Throwable, V?, Addressable, SegmentAllocator) -> V/void
}
closer = collectArguments(closer, insertPos++, MH_CLOSE_CONTEXT); // (Throwable, V?, Addressable?, BindingContext) -> V/void
if (!upcall) {
// now for each Addressable parameter, add a reachability fence
MethodType specType = specializedHandle.type();
// skip 3 for address, segment allocator, and binding context
for (int i = 3; i < specType.parameterCount(); i++) {
Class<?> param = specType.parameterType(i);
if (Addressable.class.isAssignableFrom(param)) {
closer = collectArguments(closer, insertPos++, reachabilityFenceHandle(param));
} else {
closer = dropArguments(closer, insertPos++, param);
}
}
}
MethodHandle contextFactory;
if (bufferCopySize > 0) {
contextFactory = MethodHandles.insertArguments(MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR, 0, bufferCopySize);
} else if (upcall) {
contextFactory = MH_MAKE_CONTEXT_NO_ALLOCATOR;
} else {
// this path is probably never used now, since ProgrammableInvoker never calls this routine with bufferCopySize == 0
contextFactory = constant(Binding.Context.class, Binding.Context.DUMMY);
}
specializedHandle = tryFinally(specializedHandle, closer);
specializedHandle = collectArguments(specializedHandle, allocatorPos, contextFactory);
return specializedHandle;
}
// lazy init MH_ALLOC and MH_FREE handles // lazy init MH_ALLOC and MH_FREE handles
private static class AllocHolder { private static class AllocHolder {
final static LibraryLookup LOOKUP = LibraryLookup.ofDefault(); static final LibraryLookup LOOKUP = LibraryLookup.ofDefault();
final static MethodHandle MH_MALLOC = getSystemLinker().downcallHandle(LOOKUP.lookup("malloc").get(), static final MethodHandle MH_MALLOC = getSystemLinker().downcallHandle(LOOKUP.lookup("malloc").get(),
MethodType.methodType(MemoryAddress.class, long.class), MethodType.methodType(MemoryAddress.class, long.class),
FunctionDescriptor.of(C_POINTER, C_LONG_LONG)); FunctionDescriptor.of(C_POINTER, C_LONG_LONG));
final static MethodHandle MH_FREE = getSystemLinker().downcallHandle(LOOKUP.lookup("free").get(), static final MethodHandle MH_FREE = getSystemLinker().downcallHandle(LOOKUP.lookup("free").get(),
MethodType.methodType(void.class, MemoryAddress.class), MethodType.methodType(void.class, MemoryAddress.class),
FunctionDescriptor.ofVoid(C_POINTER)); FunctionDescriptor.ofVoid(C_POINTER));
} }
@ -294,25 +442,25 @@ public class SharedUtils {
} }
} }
public static VaList newVaList(Consumer<VaList.Builder> actions, Allocator allocator) { public static VaList newVaList(Consumer<VaList.Builder> actions, ResourceScope scope) {
return switch (CABI.current()) { return switch (CABI.current()) {
case Win64 -> Windowsx64Linker.newVaList(actions, allocator); case Win64 -> Windowsx64Linker.newVaList(actions, scope);
case SysV -> SysVx64Linker.newVaList(actions, allocator); case SysV -> SysVx64Linker.newVaList(actions, scope);
case AArch64 -> AArch64Linker.newVaList(actions, allocator); case AArch64 -> AArch64Linker.newVaList(actions, scope);
}; };
} }
public static VarHandle vhPrimitiveOrAddress(Class<?> carrier, MemoryLayout layout) { public static VarHandle vhPrimitiveOrAddress(Class<?> carrier, MemoryLayout layout) {
return carrier == MemoryAddress.class return carrier == MemoryAddress.class
? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize()))) ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize(), false)))
: layout.varHandle(carrier); : layout.varHandle(carrier);
} }
public static VaList newVaListOfAddress(MemoryAddress ma) { public static VaList newVaListOfAddress(MemoryAddress ma, ResourceScope scope) {
return switch (CABI.current()) { return switch (CABI.current()) {
case Win64 -> Windowsx64Linker.newVaListOfAddress(ma); case Win64 -> Windowsx64Linker.newVaListOfAddress(ma, scope);
case SysV -> SysVx64Linker.newVaListOfAddress(ma); case SysV -> SysVx64Linker.newVaListOfAddress(ma, scope);
case AArch64 -> AArch64Linker.newVaListOfAddress(ma); case AArch64 -> AArch64Linker.newVaListOfAddress(ma, scope);
}; };
} }
@ -336,7 +484,7 @@ public class SharedUtils {
public static MethodHandle unboxVaLists(MethodType type, MethodHandle handle, MethodHandle unboxer) { public static MethodHandle unboxVaLists(MethodType type, MethodHandle handle, MethodHandle unboxer) {
for (int i = 0; i < type.parameterCount(); i++) { for (int i = 0; i < type.parameterCount(); i++) {
if (type.parameterType(i) == VaList.class) { if (type.parameterType(i) == VaList.class) {
handle = MethodHandles.filterArguments(handle, i, unboxer); handle = filterArguments(handle, i + 1, unboxer); // +1 for leading address
} }
} }
return handle; return handle;
@ -346,7 +494,7 @@ public class SharedUtils {
MethodType type = handle.type(); MethodType type = handle.type();
for (int i = 0; i < type.parameterCount(); i++) { for (int i = 0; i < type.parameterCount(); i++) {
if (type.parameterType(i) == VaList.class) { if (type.parameterType(i) == VaList.class) {
handle = MethodHandles.filterArguments(handle, i, boxer); handle = filterArguments(handle, i, boxer);
} }
} }
return handle; return handle;
@ -365,37 +513,6 @@ public class SharedUtils {
.orElse(false); .orElse(false);
} }
public interface Allocator extends AutoCloseable {
Allocator THROWING_ALLOCATOR = (size, align) -> { throw new UnsupportedOperationException("Null allocator"); };
default MemorySegment allocate(MemoryLayout layout) {
return allocate(layout.byteSize(), layout.byteAlignment());
}
default MemorySegment allocate(long size) {
return allocate(size, 1);
}
@Override
default void close() {}
MemorySegment allocate(long size, long align);
static Allocator ofScope(NativeScope scope) {
return new Allocator() {
@Override
public MemorySegment allocate(long size, long align) {
return scope.allocate(size, align);
}
@Override
public void close() {
scope.close();
}
};
}
}
public static class SimpleVaArg { public static class SimpleVaArg {
public final Class<?> carrier; public final Class<?> carrier;
public final MemoryLayout layout; public final MemoryLayout layout;
@ -409,12 +526,12 @@ public class SharedUtils {
public VarHandle varHandle() { public VarHandle varHandle() {
return carrier == MemoryAddress.class return carrier == MemoryAddress.class
? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize()))) ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize(), false)))
: layout.varHandle(carrier); : layout.varHandle(carrier);
} }
} }
public static class EmptyVaList implements VaList { public static non-sealed class EmptyVaList implements VaList {
private final MemoryAddress address; private final MemoryAddress address;
@ -447,12 +564,12 @@ public class SharedUtils {
} }
@Override @Override
public MemorySegment vargAsSegment(MemoryLayout layout) { public MemorySegment vargAsSegment(MemoryLayout layout, SegmentAllocator allocator) {
throw uoe(); throw uoe();
} }
@Override @Override
public MemorySegment vargAsSegment(MemoryLayout layout, NativeScope scope) { public MemorySegment vargAsSegment(MemoryLayout layout, ResourceScope scope) {
throw uoe(); throw uoe();
} }
@ -462,13 +579,8 @@ public class SharedUtils {
} }
@Override @Override
public boolean isAlive() { public ResourceScope scope() {
return true; return ResourceScope.globalScope();
}
@Override
public void close() {
throw uoe();
} }
@Override @Override
@ -476,11 +588,6 @@ public class SharedUtils {
return this; return this;
} }
@Override
public VaList copy(NativeScope scope) {
throw uoe();
}
@Override @Override
public MemoryAddress address() { public MemoryAddress address() {
return address; return address;

View file

@ -26,18 +26,16 @@ package jdk.internal.foreign.abi;
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.internal.foreign.MemoryAddressImpl; import jdk.internal.foreign.ResourceScopeImpl;
import jdk.internal.foreign.NativeMemorySegmentImpl; import jdk.internal.foreign.NativeMemorySegmentImpl;
public class UpcallStubs { public class UpcallStubs {
public static MemorySegment upcallAddress(UpcallHandler handler) { public static MemoryAddress upcallAddress(UpcallHandler handler, ResourceScopeImpl scope) {
long stubAddress = handler.entryPoint(); long stubAddress = handler.entryPoint();
return NativeMemorySegmentImpl.makeNativeSegmentUnchecked( return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(MemoryAddress.ofLong(stubAddress), 0,
MemoryAddress.ofLong(stubAddress), 0, () -> freeUpcallStub(stubAddress), null) () -> freeUpcallStub(stubAddress), scope).address();
.share() }
.withAccessModes(MemorySegment.CLOSE | MemorySegment.HANDOFF | MemorySegment.SHARE);
};
private static void freeUpcallStub(long stubAddress) { private static void freeUpcallStub(long stubAddress) {
if (!freeUpcallStub0(stubAddress)) { if (!freeUpcallStub0(stubAddress)) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, Arm Limited. All rights reserved. * Copyright (c) 2019, 2020, Arm Limited. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
@ -25,12 +25,12 @@
*/ */
package jdk.internal.foreign.abi.aarch64; package jdk.internal.foreign.abi.aarch64;
import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.FunctionDescriptor; import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.CLinker; import jdk.incubator.foreign.ResourceScope;
import jdk.internal.foreign.AbstractCLinker;
import jdk.internal.foreign.ResourceScopeImpl;
import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.abi.UpcallStubs; import jdk.internal.foreign.abi.UpcallStubs;
@ -40,13 +40,11 @@ import java.lang.invoke.MethodType;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import static jdk.internal.foreign.PlatformLayouts.*;
/** /**
* ABI implementation based on ARM document "Procedure Call Standard for * ABI implementation based on ARM document "Procedure Call Standard for
* the ARM 64-bit Architecture". * the ARM 64-bit Architecture".
*/ */
public class AArch64Linker implements CLinker { public final class AArch64Linker extends AbstractCLinker {
private static AArch64Linker instance; private static AArch64Linker instance;
static final long ADDRESS_SIZE = 64; // bits static final long ADDRESS_SIZE = 64; // bits
@ -59,8 +57,8 @@ public class AArch64Linker implements CLinker {
MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_unboxVaList = lookup.findVirtual(VaList.class, "address", MH_unboxVaList = lookup.findVirtual(VaList.class, "address",
MethodType.methodType(MemoryAddress.class)); MethodType.methodType(MemoryAddress.class));
MH_boxVaList = lookup.findStatic(AArch64Linker.class, "newVaListOfAddress", MH_boxVaList = MethodHandles.insertArguments(lookup.findStatic(AArch64Linker.class, "newVaListOfAddress",
MethodType.methodType(VaList.class, MemoryAddress.class)); MethodType.methodType(VaList.class, MemoryAddress.class, ResourceScope.class)), 1, ResourceScope.globalScope());
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e); throw new ExceptionInInitializerError(e);
} }
@ -74,32 +72,36 @@ public class AArch64Linker implements CLinker {
} }
@Override @Override
public MethodHandle downcallHandle(Addressable symbol, MethodType type, FunctionDescriptor function) { public final MethodHandle downcallHandle(MethodType type, FunctionDescriptor function) {
Objects.requireNonNull(symbol);
Objects.requireNonNull(type); Objects.requireNonNull(type);
Objects.requireNonNull(function); Objects.requireNonNull(function);
MethodType llMt = SharedUtils.convertVaListCarriers(type, AArch64VaList.CARRIER); MethodType llMt = SharedUtils.convertVaListCarriers(type, AArch64VaList.CARRIER);
MethodHandle handle = CallArranger.arrangeDowncall(symbol, llMt, function); MethodHandle handle = CallArranger.arrangeDowncall(llMt, function);
if (!type.returnType().equals(MemorySegment.class)) {
// not returning segment, just insert a throwing allocator
handle = MethodHandles.insertArguments(handle, 1, SharedUtils.THROWING_ALLOCATOR);
}
handle = SharedUtils.unboxVaLists(type, handle, MH_unboxVaList); handle = SharedUtils.unboxVaLists(type, handle, MH_unboxVaList);
return handle; return handle;
} }
@Override @Override
public MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function) { public final MemoryAddress upcallStub(MethodHandle target, FunctionDescriptor function, ResourceScope scope) {
Objects.requireNonNull(scope);
Objects.requireNonNull(target); Objects.requireNonNull(target);
Objects.requireNonNull(function); Objects.requireNonNull(function);
target = SharedUtils.boxVaLists(target, MH_boxVaList); target = SharedUtils.boxVaLists(target, MH_boxVaList);
return UpcallStubs.upcallAddress(CallArranger.arrangeUpcall(target, target.type(), function)); return UpcallStubs.upcallAddress(CallArranger.arrangeUpcall(target, target.type(), function), (ResourceScopeImpl) scope);
} }
public static VaList newVaList(Consumer<VaList.Builder> actions, SharedUtils.Allocator allocator) { public static VaList newVaList(Consumer<VaList.Builder> actions, ResourceScope scope) {
AArch64VaList.Builder builder = AArch64VaList.builder(allocator); AArch64VaList.Builder builder = AArch64VaList.builder(scope);
actions.accept(builder); actions.accept(builder);
return builder.build(); return builder.build();
} }
public static VaList newVaListOfAddress(MemoryAddress ma) { public static VaList newVaListOfAddress(MemoryAddress ma, ResourceScope scope) {
return AArch64VaList.ofAddress(ma); return AArch64VaList.ofAddress(ma, scope);
} }
public static VaList emptyVaList() { public static VaList emptyVaList() {

View file

@ -26,7 +26,6 @@
package jdk.internal.foreign.abi.aarch64; package jdk.internal.foreign.abi.aarch64;
import jdk.incubator.foreign.*; import jdk.incubator.foreign.*;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.Utils; import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.misc.Unsafe; import jdk.internal.misc.Unsafe;
@ -42,11 +41,12 @@ import static jdk.internal.foreign.PlatformLayouts.AArch64;
import static jdk.incubator.foreign.CLinker.VaList; import static jdk.incubator.foreign.CLinker.VaList;
import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement; import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement;
import static jdk.internal.foreign.abi.SharedUtils.SimpleVaArg; import static jdk.internal.foreign.abi.SharedUtils.SimpleVaArg;
import static jdk.internal.foreign.abi.SharedUtils.THROWING_ALLOCATOR;
import static jdk.internal.foreign.abi.SharedUtils.checkCompatibleType; import static jdk.internal.foreign.abi.SharedUtils.checkCompatibleType;
import static jdk.internal.foreign.abi.SharedUtils.vhPrimitiveOrAddress; import static jdk.internal.foreign.abi.SharedUtils.vhPrimitiveOrAddress;
import static jdk.internal.foreign.abi.aarch64.CallArranger.MAX_REGISTER_ARGUMENTS; import static jdk.internal.foreign.abi.aarch64.CallArranger.MAX_REGISTER_ARGUMENTS;
public class AArch64VaList implements VaList { public non-sealed class AArch64VaList implements VaList {
private static final Unsafe U = Unsafe.getUnsafe(); private static final Unsafe U = Unsafe.getUnsafe();
static final Class<?> CARRIER = MemoryAddress.class; static final Class<?> CARRIER = MemoryAddress.class;
@ -62,7 +62,7 @@ public class AArch64VaList implements VaList {
// int __vr_offs; // offset from __vr_top to next FP/SIMD register arg // int __vr_offs; // offset from __vr_top to next FP/SIMD register arg
// } va_list; // } va_list;
static final GroupLayout LAYOUT = MemoryLayout.ofStruct( static final GroupLayout LAYOUT = MemoryLayout.structLayout(
AArch64.C_POINTER.withName("__stack"), AArch64.C_POINTER.withName("__stack"),
AArch64.C_POINTER.withName("__gr_top"), AArch64.C_POINTER.withName("__gr_top"),
AArch64.C_POINTER.withName("__vr_top"), AArch64.C_POINTER.withName("__vr_top"),
@ -71,14 +71,14 @@ public class AArch64VaList implements VaList {
).withName("__va_list"); ).withName("__va_list");
private static final MemoryLayout GP_REG private static final MemoryLayout GP_REG
= MemoryLayout.ofValueBits(64, ByteOrder.nativeOrder()); = MemoryLayout.valueLayout(64, ByteOrder.nativeOrder());
private static final MemoryLayout FP_REG private static final MemoryLayout FP_REG
= MemoryLayout.ofValueBits(128, ByteOrder.nativeOrder()); = MemoryLayout.valueLayout(128, ByteOrder.nativeOrder());
private static final MemoryLayout LAYOUT_GP_REGS private static final MemoryLayout LAYOUT_GP_REGS
= MemoryLayout.ofSequence(MAX_REGISTER_ARGUMENTS, GP_REG); = MemoryLayout.sequenceLayout(MAX_REGISTER_ARGUMENTS, GP_REG);
private static final MemoryLayout LAYOUT_FP_REGS private static final MemoryLayout LAYOUT_FP_REGS
= MemoryLayout.ofSequence(MAX_REGISTER_ARGUMENTS, FP_REG); = MemoryLayout.sequenceLayout(MAX_REGISTER_ARGUMENTS, FP_REG);
private static final int GP_SLOT_SIZE = (int) GP_REG.byteSize(); private static final int GP_SLOT_SIZE = (int) GP_REG.byteSize();
private static final int FP_SLOT_SIZE = (int) FP_REG.byteSize(); private static final int FP_SLOT_SIZE = (int) FP_REG.byteSize();
@ -104,31 +104,27 @@ public class AArch64VaList implements VaList {
private final MemorySegment segment; private final MemorySegment segment;
private final MemorySegment gpRegsArea; private final MemorySegment gpRegsArea;
private final MemorySegment fpRegsArea; private final MemorySegment fpRegsArea;
private final List<MemorySegment> attachedSegments;
private AArch64VaList(MemorySegment segment, MemorySegment gpRegsArea, MemorySegment fpRegsArea, private AArch64VaList(MemorySegment segment, MemorySegment gpRegsArea, MemorySegment fpRegsArea) {
List<MemorySegment> attachedSegments) {
this.segment = segment; this.segment = segment;
this.gpRegsArea = gpRegsArea; this.gpRegsArea = gpRegsArea;
this.fpRegsArea = fpRegsArea; this.fpRegsArea = fpRegsArea;
this.attachedSegments = attachedSegments;
} }
private static AArch64VaList readFromSegment(MemorySegment segment) { private static AArch64VaList readFromSegment(MemorySegment segment) {
MemorySegment gpRegsArea = handoffIfNeeded(grTop(segment).addOffset(-MAX_GP_OFFSET) MemorySegment gpRegsArea = grTop(segment).addOffset(-MAX_GP_OFFSET).asSegment(
.asSegmentRestricted(MAX_GP_OFFSET), segment.ownerThread()); MAX_GP_OFFSET, segment.scope());
MemorySegment fpRegsArea = handoffIfNeeded(vrTop(segment).addOffset(-MAX_FP_OFFSET) MemorySegment fpRegsArea = vrTop(segment).addOffset(-MAX_FP_OFFSET).asSegment(
.asSegmentRestricted(MAX_FP_OFFSET), segment.ownerThread()); MAX_FP_OFFSET, segment.scope());
return new AArch64VaList(segment, gpRegsArea, fpRegsArea, List.of(gpRegsArea, fpRegsArea)); return new AArch64VaList(segment, gpRegsArea, fpRegsArea);
} }
private static MemoryAddress emptyListAddress() { private static MemoryAddress emptyListAddress() {
long ptr = U.allocateMemory(LAYOUT.byteSize()); long ptr = U.allocateMemory(LAYOUT.byteSize());
MemorySegment ms = MemoryAddress.ofLong(ptr) MemorySegment ms = MemoryAddress.ofLong(ptr).asSegment(
.asSegmentRestricted(LAYOUT.byteSize(), () -> U.freeMemory(ptr), null) LAYOUT.byteSize(), () -> U.freeMemory(ptr), ResourceScope.newSharedScope());
.share(); cleaner.register(AArch64VaList.class, () -> ms.scope().close());
cleaner.register(AArch64VaList.class, ms::close);
VH_stack.set(ms, MemoryAddress.NULL); VH_stack.set(ms, MemoryAddress.NULL);
VH_gr_top.set(ms, MemoryAddress.NULL); VH_gr_top.set(ms, MemoryAddress.NULL);
VH_vr_top.set(ms, MemoryAddress.NULL); VH_vr_top.set(ms, MemoryAddress.NULL);
@ -234,21 +230,21 @@ public class AArch64VaList implements VaList {
} }
@Override @Override
public MemorySegment vargAsSegment(MemoryLayout layout) { public MemorySegment vargAsSegment(MemoryLayout layout, SegmentAllocator allocator) {
return (MemorySegment) read(MemorySegment.class, layout); Objects.requireNonNull(allocator);
return (MemorySegment) read(MemorySegment.class, layout, allocator);
} }
@Override @Override
public MemorySegment vargAsSegment(MemoryLayout layout, NativeScope scope) { public MemorySegment vargAsSegment(MemoryLayout layout, ResourceScope scope) {
Objects.requireNonNull(scope); return vargAsSegment(layout, SegmentAllocator.ofScope(scope));
return (MemorySegment) read(MemorySegment.class, layout, SharedUtils.Allocator.ofScope(scope));
} }
private Object read(Class<?> carrier, MemoryLayout layout) { private Object read(Class<?> carrier, MemoryLayout layout) {
return read(carrier, layout, MemorySegment::allocateNative); return read(carrier, layout, THROWING_ALLOCATOR);
} }
private Object read(Class<?> carrier, MemoryLayout layout, SharedUtils.Allocator allocator) { private Object read(Class<?> carrier, MemoryLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(layout); Objects.requireNonNull(layout);
checkCompatibleType(carrier, layout, AArch64Linker.ADDRESS_SIZE); checkCompatibleType(carrier, layout, AArch64Linker.ADDRESS_SIZE);
@ -257,23 +253,19 @@ public class AArch64VaList implements VaList {
preAlignStack(layout); preAlignStack(layout);
return switch (typeClass) { return switch (typeClass) {
case STRUCT_REGISTER, STRUCT_HFA, STRUCT_REFERENCE -> { case STRUCT_REGISTER, STRUCT_HFA, STRUCT_REFERENCE -> {
try (MemorySegment slice = handoffIfNeeded(stackPtr() MemorySegment slice = stackPtr().asSegment(layout.byteSize(), scope());
.asSegmentRestricted(layout.byteSize()), segment.ownerThread())) {
MemorySegment seg = allocator.allocate(layout); MemorySegment seg = allocator.allocate(layout);
seg.copyFrom(slice); seg.copyFrom(slice);
postAlignStack(layout); postAlignStack(layout);
yield seg; yield seg;
} }
}
case POINTER, INTEGER, FLOAT -> { case POINTER, INTEGER, FLOAT -> {
VarHandle reader = vhPrimitiveOrAddress(carrier, layout); VarHandle reader = vhPrimitiveOrAddress(carrier, layout);
try (MemorySegment slice = handoffIfNeeded(stackPtr() MemorySegment slice = stackPtr().asSegment(layout.byteSize(), scope());
.asSegmentRestricted(layout.byteSize()), segment.ownerThread())) {
Object res = reader.get(slice); Object res = reader.get(slice);
postAlignStack(layout); postAlignStack(layout);
yield res; yield res;
} }
}
}; };
} else { } else {
return switch (typeClass) { return switch (typeClass) {
@ -314,13 +306,11 @@ public class AArch64VaList implements VaList {
gpRegsArea.asSlice(currentGPOffset())); gpRegsArea.asSlice(currentGPOffset()));
consumeGPSlots(1); consumeGPSlots(1);
try (MemorySegment slice = handoffIfNeeded(ptr MemorySegment slice = ptr.asSegment(layout.byteSize(), scope());
.asSegmentRestricted(layout.byteSize()), segment.ownerThread())) {
MemorySegment seg = allocator.allocate(layout); MemorySegment seg = allocator.allocate(layout);
seg.copyFrom(slice); seg.copyFrom(slice);
yield seg; yield seg;
} }
}
case POINTER, INTEGER -> { case POINTER, INTEGER -> {
VarHandle reader = SharedUtils.vhPrimitiveOrAddress(carrier, layout); VarHandle reader = SharedUtils.vhPrimitiveOrAddress(carrier, layout);
Object res = reader.get(gpRegsArea.asSlice(currentGPOffset())); Object res = reader.get(gpRegsArea.asSlice(currentGPOffset()));
@ -356,40 +346,24 @@ public class AArch64VaList implements VaList {
} }
} }
static AArch64VaList.Builder builder(SharedUtils.Allocator allocator) { static AArch64VaList.Builder builder(ResourceScope scope) {
return new AArch64VaList.Builder(allocator); return new AArch64VaList.Builder(scope);
} }
public static VaList ofAddress(MemoryAddress ma) { public static VaList ofAddress(MemoryAddress ma, ResourceScope scope) {
return readFromSegment(ma.asSegmentRestricted(LAYOUT.byteSize())); return readFromSegment(ma.asSegment(LAYOUT.byteSize(), scope));
} }
@Override @Override
public boolean isAlive() { public ResourceScope scope() {
return segment.isAlive(); return segment.scope();
}
@Override
public void close() {
segment.close();
attachedSegments.forEach(MemorySegment::close);
} }
@Override @Override
public VaList copy() { public VaList copy() {
return copy(MemorySegment::allocateNative); MemorySegment copy = MemorySegment.allocateNative(LAYOUT, segment.scope());
}
@Override
public VaList copy(NativeScope scope) {
Objects.requireNonNull(scope);
return copy(SharedUtils.Allocator.ofScope(scope));
}
private VaList copy(SharedUtils.Allocator allocator) {
MemorySegment copy = allocator.allocate(LAYOUT);
copy.copyFrom(segment); copy.copyFrom(segment);
return new AArch64VaList(copy, gpRegsArea, fpRegsArea, List.of()); return new AArch64VaList(copy, gpRegsArea, fpRegsArea);
} }
@Override @Override
@ -423,8 +397,8 @@ public class AArch64VaList implements VaList {
+ '}'; + '}';
} }
static class Builder implements VaList.Builder { public static non-sealed class Builder implements VaList.Builder {
private final SharedUtils.Allocator allocator; private final ResourceScope scope;
private final MemorySegment gpRegs; private final MemorySegment gpRegs;
private final MemorySegment fpRegs; private final MemorySegment fpRegs;
@ -432,10 +406,10 @@ public class AArch64VaList implements VaList {
private long currentFPOffset = 0; private long currentFPOffset = 0;
private final List<SimpleVaArg> stackArgs = new ArrayList<>(); private final List<SimpleVaArg> stackArgs = new ArrayList<>();
Builder(SharedUtils.Allocator allocator) { Builder(ResourceScope scope) {
this.allocator = allocator; this.scope = scope;
this.gpRegs = allocator.allocate(LAYOUT_GP_REGS); this.gpRegs = MemorySegment.allocateNative(LAYOUT_GP_REGS, scope);
this.fpRegs = allocator.allocate(LAYOUT_FP_REGS); this.fpRegs = MemorySegment.allocateNative(LAYOUT_FP_REGS, scope);
} }
@Override @Override
@ -534,8 +508,8 @@ public class AArch64VaList implements VaList {
return EMPTY; return EMPTY;
} }
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
MemorySegment vaListSegment = allocator.allocate(LAYOUT); MemorySegment vaListSegment = allocator.allocate(LAYOUT);
List<MemorySegment> attachedSegments = new ArrayList<>();
MemoryAddress stackArgsPtr = MemoryAddress.NULL; MemoryAddress stackArgsPtr = MemoryAddress.NULL;
if (!stackArgs.isEmpty()) { if (!stackArgs.isEmpty()) {
long stackArgsSize = stackArgs.stream() long stackArgsSize = stackArgs.stream()
@ -549,7 +523,6 @@ public class AArch64VaList implements VaList {
writer.set(stackArgsSegment, arg.value); writer.set(stackArgsSegment, arg.value);
stackArgsSegment = stackArgsSegment.asSlice(alignedSize); stackArgsSegment = stackArgsSegment.asSlice(alignedSize);
} }
attachedSegments.add(stackArgsSegment);
} }
VH_gr_top.set(vaListSegment, gpRegs.asSlice(gpRegs.byteSize()).address()); VH_gr_top.set(vaListSegment, gpRegs.asSlice(gpRegs.byteSize()).address());
@ -558,16 +531,9 @@ public class AArch64VaList implements VaList {
VH_gr_offs.set(vaListSegment, -MAX_GP_OFFSET); VH_gr_offs.set(vaListSegment, -MAX_GP_OFFSET);
VH_vr_offs.set(vaListSegment, -MAX_FP_OFFSET); VH_vr_offs.set(vaListSegment, -MAX_FP_OFFSET);
attachedSegments.add(gpRegs); assert gpRegs.scope().ownerThread() == vaListSegment.scope().ownerThread();
attachedSegments.add(fpRegs); assert fpRegs.scope().ownerThread() == vaListSegment.scope().ownerThread();
assert gpRegs.ownerThread() == vaListSegment.ownerThread(); return new AArch64VaList(vaListSegment, gpRegs, fpRegs);
assert fpRegs.ownerThread() == vaListSegment.ownerThread();
return new AArch64VaList(vaListSegment, gpRegs, fpRegs, attachedSegments);
} }
} }
private static MemorySegment handoffIfNeeded(MemorySegment segment, Thread thread) {
return segment.ownerThread() == thread ?
segment : segment.handoff(thread);
}
} }

Some files were not shown because too many files have changed in this diff Show more