Merge branch 'openjdk:master' into JDK-8373118

This commit is contained in:
Doug Lea 2026-01-12 12:18:41 -05:00 committed by GitHub
commit c3e137a6bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 900 additions and 119 deletions

View File

@ -458,12 +458,10 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) {
warning("Failed mmap to file. (%s)", os::strerror(errno));
return nullptr;
}
if (base != nullptr && addr != base) {
if (!os::release_memory(addr, size)) {
warning("Could not release memory on unsuccessful file mapping");
}
return nullptr;
}
// The requested address should be the same as the returned address when using MAP_FIXED
// as per POSIX.
assert(base == nullptr || addr == base, "base should equal addr when using MAP_FIXED");
return addr;
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021 SAP SE. All rights reserved.
* Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026 SAP SE. 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
@ -946,7 +946,7 @@ static int create_sharedmem_file(const char* dirname, const char* filename, size
if (result == -1 ) break;
if (!os::write(fd, &zero_int, 1)) {
if (errno == ENOSPC) {
warning("Insufficient space for shared memory file:\n %s\nTry using the -Djava.io.tmpdir= option to select an alternate temp location.\n", filename);
warning("Insufficient space for shared memory file: %s/%s\n", dirname, filename);
}
result = OS_ERR;
break;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2026, 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
@ -686,7 +686,8 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
// the check before we do the actual allocation. The reason for doing it
// before the allocation is that we avoid having to keep track of the newly
// allocated memory while we do a GC.
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
// Only try that if we can actually perform a GC.
if (is_init_completed() && policy()->need_to_start_conc_mark("concurrent humongous allocation",
word_size)) {
try_collect(word_size, GCCause::_g1_humongous_allocation, collection_counters(this));
}

View File

@ -700,7 +700,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) {
// then someone else has made such a claim and the deferred update has not
// yet been processed and will include our change, so we don't need to do
// anything further.
if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) {
if (_deferred_updates_next.compare_set(nullptr, this)) {
// Successfully claimed. Push, with self-loop for end-of-list.
Block* head = owner->_deferred_updates.load_relaxed();
while (true) {

View File

@ -56,7 +56,7 @@ void PretouchTask::work(uint worker_id) {
char* cur_end = cur_start + MIN2(_chunk_size, pointer_delta(_end_addr, cur_start, 1));
if (cur_start >= cur_end) {
break;
} else if (cur_start == _cur_addr.compare_exchange(cur_start, cur_end)) {
} else if (_cur_addr.compare_set(cur_start, cur_end)) {
os::pretouch_memory(cur_start, cur_end, _page_size);
} // Else attempt to claim chunk failed, so try again.
}

View File

@ -183,8 +183,8 @@ protected:
_age.store_relaxed(new_age);
}
Age cmpxchg_age(Age old_age, Age new_age) {
return _age.compare_exchange(old_age, new_age);
bool par_set_age(Age old_age, Age new_age) {
return _age.compare_set(old_age, new_age);
}
idx_t age_top_relaxed() const {
@ -345,7 +345,7 @@ protected:
using TaskQueueSuper<N, MT>::age_relaxed;
using TaskQueueSuper<N, MT>::set_age_relaxed;
using TaskQueueSuper<N, MT>::cmpxchg_age;
using TaskQueueSuper<N, MT>::par_set_age;
using TaskQueueSuper<N, MT>::age_top_relaxed;
using TaskQueueSuper<N, MT>::increment_index;

View File

@ -170,8 +170,7 @@ bool GenericTaskQueue<E, MT, N>::pop_local_slow(uint localBot, Age oldAge) {
if (localBot == oldAge.top()) {
// No competing pop_global has yet incremented "top"; we'll try to
// install new_age, thus claiming the element.
Age tempAge = cmpxchg_age(oldAge, newAge);
if (tempAge == oldAge) {
if (par_set_age(oldAge, newAge)) {
// We win.
assert_not_underflow(localBot, age_top_relaxed());
TASKQUEUE_STATS_ONLY(stats.record_pop_slow());
@ -283,12 +282,12 @@ typename GenericTaskQueue<E, MT, N>::PopResult GenericTaskQueue<E, MT, N>::pop_g
idx_t new_top = increment_index(oldAge.top());
idx_t new_tag = oldAge.tag() + ((new_top == 0) ? 1 : 0);
Age newAge(new_top, new_tag);
Age resAge = cmpxchg_age(oldAge, newAge);
bool result = par_set_age(oldAge, newAge);
// Note that using "bottom" here might fail, since a pop_local might
// have decremented it.
assert_not_underflow(localBot, newAge.top());
return resAge == oldAge ? PopResult::Success : PopResult::Contended;
return result ? PopResult::Success : PopResult::Contended;
}
inline int randomParkAndMiller(int *seed0) {

View File

@ -1112,8 +1112,6 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
if( !ti->is_con() ) return nullptr;
jint con = ti->get_con();
Node *hook = new Node(1);
// First, special check for modulo 2^k-1
if( con >= 0 && con < max_jint && is_power_of_2(con+1) ) {
uint k = exact_log2(con+1); // Extract k
@ -1129,7 +1127,9 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
Node *x = in(1); // Value being mod'd
Node *divisor = in(2); // Also is mask
hook->init_req(0, x); // Add a use to x to prevent him from dying
// Add a use to x to prevent it from dying
Node* hook = new Node(1);
hook->init_req(0, x);
// Generate code to reduce X rapidly to nearly 2^k-1.
for( int i = 0; i < trip_count; i++ ) {
Node *xl = phase->transform( new AndINode(x,divisor) );
@ -1185,6 +1185,7 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
// Save in(1) so that it cannot be changed or deleted
Node* hook = new Node(1);
hook->init_req(0, in(1));
// Divide using the transform from DivI to MulL
@ -1407,8 +1408,6 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if( !tl->is_con() ) return nullptr;
jlong con = tl->get_con();
Node *hook = new Node(1);
// Expand mod
if(con >= 0 && con < max_jlong && is_power_of_2(con + 1)) {
uint k = log2i_exact(con + 1); // Extract k
@ -1426,13 +1425,15 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
Node *x = in(1); // Value being mod'd
Node *divisor = in(2); // Also is mask
hook->init_req(0, x); // Add a use to x to prevent him from dying
// Add a use to x to prevent it from dying
Node* hook = new Node(1);
hook->init_req(0, x);
// Generate code to reduce X rapidly to nearly 2^k-1.
for( int i = 0; i < trip_count; i++ ) {
Node *xl = phase->transform( new AndLNode(x,divisor) );
Node *xh = phase->transform( new RShiftLNode(x,phase->intcon(k)) ); // Must be signed
x = phase->transform( new AddLNode(xh,xl) );
hook->set_req(0, x); // Add a use to x to prevent him from dying
hook->set_req(0, x); // Add a use to x to prevent it from dying
}
// Generate sign-fixup code. Was original value positive?
@ -1482,6 +1483,8 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
// Save in(1) so that it cannot be changed or deleted
// Add a use to x to prevent him from dying
Node* hook = new Node(1);
hook->init_req(0, in(1));
// Divide using the transform from DivL to MulL

View File

@ -75,6 +75,7 @@
// v.release_store(x) -> void
// v.release_store_fence(x) -> void
// v.compare_exchange(x, y [, o]) -> T
// v.compare_set(x, y [, o]) -> bool
// v.exchange(x [, o]) -> T
//
// (2) All atomic types are default constructible.
@ -267,6 +268,11 @@ public:
return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order);
}
bool compare_set(T compare_value, T new_value,
atomic_memory_order order = memory_order_conservative) {
return compare_exchange(compare_value, new_value, order) == compare_value;
}
T exchange(T new_value,
atomic_memory_order order = memory_order_conservative) {
return AtomicAccess::xchg(this->value_ptr(), new_value, order);
@ -479,6 +485,13 @@ public:
order));
}
bool compare_set(T compare_value, T new_value,
atomic_memory_order order = memory_order_conservative) {
return _value.compare_set(decay(compare_value),
decay(new_value),
order);
}
T exchange(T new_value, atomic_memory_order order = memory_order_conservative) {
return recover(_value.exchange(decay(new_value), order));
}

View File

@ -157,7 +157,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
if (is_locked()) {
return false;
}
if (_first.compare_exchange(expect, node) == expect) {
if (_first.compare_set(expect, node)) {
return true;
}
return false;
@ -172,7 +172,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
}
// We will expect a clean first pointer.
Node* tmp = first();
if (_first.compare_exchange(tmp, set_state(tmp, STATE_LOCK_BIT)) == tmp) {
if (_first.compare_set(tmp, set_state(tmp, STATE_LOCK_BIT))) {
return true;
}
return false;

View File

@ -181,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(0, waiters);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Successfully disarmed.
break;
}
@ -218,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(tag, waiters + 1);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Success! Proceed to wait.
break;
}
@ -247,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(tag, waiters - 1);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Success!
break;
}

View File

@ -2045,19 +2045,26 @@ public final class String
return encode(Charset.defaultCharset(), coder(), value);
}
boolean bytesCompatible(Charset charset) {
boolean bytesCompatible(Charset charset, int srcIndex, int numChars) {
if (isLatin1()) {
if (charset == ISO_8859_1.INSTANCE) {
return true; // ok, same encoding
} else if (charset == UTF_8.INSTANCE || charset == US_ASCII.INSTANCE) {
return !StringCoding.hasNegatives(value, 0, value.length); // ok, if ASCII-compatible
return !StringCoding.hasNegatives(value, srcIndex, numChars); // ok, if ASCII-compatible
}
}
return false;
}
void copyToSegmentRaw(MemorySegment segment, long offset) {
MemorySegment.copy(value, 0, segment, ValueLayout.JAVA_BYTE, offset, value.length);
void copyToSegmentRaw(MemorySegment segment, long offset, int srcIndex, int srcLength) {
if (!isLatin1()) {
// This method is intended to be used together with bytesCompatible, which currently only supports
// latin1 strings. In the future, bytesCompatible could be updated to handle more cases, like
// UTF-16 strings (when the platform and charset endianness match, and the String doesnt contain
// unpaired surrogates). If that happens, copyToSegmentRaw should also be updated.
throw new IllegalStateException("This string does not support copyToSegmentRaw");
}
MemorySegment.copy(value, srcIndex, segment, ValueLayout.JAVA_BYTE, offset, srcLength);
}
/**

View File

@ -2331,13 +2331,13 @@ public final class System {
}
@Override
public void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
string.copyToSegmentRaw(segment, offset);
public void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
string.copyToSegmentRaw(segment, offset, srcIndex, srcLength);
}
@Override
public boolean bytesCompatible(String string, Charset charset) {
return string.bytesCompatible(charset);
public boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
return string.bytesCompatible(charset, srcIndex, numChars);
}
});
}

View File

@ -31,6 +31,8 @@ import java.lang.classfile.Instruction;
import java.lang.classfile.Opcode;
import jdk.internal.classfile.impl.AbstractInstruction;
import jdk.internal.classfile.impl.BytecodeHelpers;
import jdk.internal.classfile.impl.Util;
/**
* Models a local variable increment instruction in the {@code code} array of a
@ -82,6 +84,37 @@ public sealed interface IncrementInstruction extends Instruction
* @throws IllegalArgumentException if {@code slot} or {@code constant} is out of range
*/
static IncrementInstruction of(int slot, int constant) {
return new AbstractInstruction.UnboundIncrementInstruction(slot, constant);
var opcode = BytecodeHelpers.validateAndIsWideIinc(slot, constant) ? Opcode.IINC_W: Opcode.IINC;
return new AbstractInstruction.UnboundIncrementInstruction(opcode, slot, constant);
}
/**
* {@return an increment instruction}
* <p>
* {@code slot} must be {@link java.lang.classfile##u1 u1} and
* {@code constant} must be within {@code [-128, 127]} for
* {@link Opcode#IINC iinc}, or {@code slot} must be
* {@link java.lang.classfile##u2 u2} and {@code constant} must be
* within {@code [-32768, 32767]} for {@link Opcode#IINC_W wide iinc}.
*
* @apiNote
* The explicit {@code op} argument allows creating {@code wide} or
* regular increment instructions when {@code slot} and
* {@code constant} can be encoded with more optimized
* increment instructions.
*
* @param op the opcode for the specific type of increment instruction,
* which must be of kind {@link Opcode.Kind#INCREMENT}
* @param slot the local variable slot to increment
* @param constant the increment constant
* @throws IllegalArgumentException if the opcode kind is not
* {@link Opcode.Kind#INCREMENT} or {@code slot} or
* {@code constant} is out of range
* @since 27
*/
static IncrementInstruction of(Opcode op, int slot, int constant) {
Util.checkKind(op, Opcode.Kind.INCREMENT);
BytecodeHelpers.validateIncrement(op, slot, constant);
return new AbstractInstruction.UnboundIncrementInstruction(op, slot, constant);
}
}

View File

@ -1296,12 +1296,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
* over the decoding process is required.
* <p>
* Getting a string from a segment with a known byte offset and
* known byte length can be done like so:
* {@snippet lang=java :
* byte[] bytes = new byte[length];
* MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, length);
* return new String(bytes, charset);
* }
* known byte length can be done using {@link #getString(long, Charset, long)}.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur
@ -1328,6 +1323,40 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
*/
String getString(long offset, Charset charset);
/**
* Reads a string from this segment at the given offset, using the provided length
* and charset.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* <p>
* If the string contains any {@code '\0'} characters, they will be read as well.
* This differs from {@link #getString(long, Charset)}, which will only read up
* to the first {@code '\0'}, resulting in truncation for string data that contains
* the {@code '\0'} character.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur
* @param charset the charset used to {@linkplain Charset#newDecoder() decode} the
* string bytes
* @param byteLength length, in bytes, of the region of memory to read and decode into
* a string
* @return a Java string constructed from the bytes read from the given starting
* address up to the given length
* @throws IllegalArgumentException if the size of the string is greater than the
* largest string supported by the platform
* @throws IndexOutOfBoundsException if {@code offset < 0}
* @throws IndexOutOfBoundsException if {@code offset > byteSize() - byteLength}
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
* this segment is not {@linkplain Scope#isAlive() alive}
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}
* @throws IllegalArgumentException if {@code byteLength < 0}
*/
String getString(long offset, Charset charset, long byteLength);
/**
* Writes the given string into this segment at the given offset, converting it to
* a null-terminated byte sequence using the {@linkplain StandardCharsets#UTF_8 UTF-8}
@ -1366,7 +1395,8 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
* will appear truncated when read again. The string can be read without
* truncation using {@link #getString(long, Charset, long)}.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur, the final address of this write
@ -2606,6 +2636,50 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
elementCount);
}
/**
* Copies the byte sequence of the given string encoded using the provided charset
* to the destination segment.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again. The string can be read without
* truncation using {@link #getString(long, Charset, long)}.
*
* @param src the Java string to be written into the destination segment
* @param dstEncoding the charset used to {@linkplain Charset#newEncoder() encode}
* the string bytes.
* @param srcIndex the starting character index of the source string
* @param dst the destination segment
* @param dstOffset the starting offset, in bytes, of the destination segment
* @param numChars the number of characters to be copied
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
* {@code dst} is not {@linkplain Scope#isAlive() alive}
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code dst.isAccessibleBy(T) == false}
* @throws IndexOutOfBoundsException if either {@code srcIndex}, {@code numChars}, or {@code dstOffset}
* are {@code < 0}
* @throws IndexOutOfBoundsException if {@code srcIndex > src.length() - numChars}
* @throws IllegalArgumentException if {@code dst} is {@linkplain #isReadOnly() read-only}
* @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - B} where {@code B} is the size,
* in bytes, of the substring of {@code src} encoded using the given charset
* @return the number of copied bytes.
*/
@ForceInline
static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
Objects.requireNonNull(src);
Objects.requireNonNull(dstEncoding);
Objects.requireNonNull(dst);
Objects.checkFromIndexSize(srcIndex, numChars, src.length());
return AbstractMemorySegmentImpl.copy(src, dstEncoding, srcIndex, dst, dstOffset, numChars);
}
/**
* Finds and returns the relative offset, in bytes, of the first mismatch between the
* source and the destination segments. More specifically, the bytes at offset

View File

@ -111,7 +111,8 @@ public interface SegmentAllocator {
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
* will appear truncated when read again. The string can be read without
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
*
* @param str the Java string to be converted into a C string
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
@ -137,10 +138,10 @@ public interface SegmentAllocator {
int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize();
MemorySegment segment;
int length;
if (StringSupport.bytesCompatible(str, charset)) {
if (StringSupport.bytesCompatible(str, charset, 0, str.length())) {
length = str.length();
segment = allocateNoInit((long) length + termCharSize);
StringSupport.copyToSegmentRaw(str, segment, 0);
StringSupport.copyToSegmentRaw(str, segment, 0, 0, str.length());
} else {
byte[] bytes = str.getBytes(charset);
length = bytes.length;
@ -153,6 +154,53 @@ public interface SegmentAllocator {
return segment;
}
/**
* Encodes a Java string using the provided charset and stores the resulting
* byte array into a 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.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again. The string can be read without
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
*
* @param str the Java string to be encoded
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
* string bytes
* @param srcIndex the starting index of the source string
* @param numChars the number of characters to be copied
* @return a new native segment containing the encoded string
* @throws IndexOutOfBoundsException if either {@code srcIndex} or {@code numChars} are {@code < 0}
* @throws IndexOutOfBoundsException if {@code srcIndex > str.length() - numChars}
*
* @implSpec The default implementation for this method copies the contents of the
* provided Java string into a new memory segment obtained by calling
* {@code this.allocate(B)}, where {@code B} is the size, in bytes, of
* the string encoded using the provided charset
* (e.g. {@code str.getBytes(charset).length});
*/
@ForceInline
default MemorySegment allocateFrom(String str, Charset charset, int srcIndex, int numChars) {
Objects.requireNonNull(charset);
Objects.requireNonNull(str);
Objects.checkFromIndexSize(srcIndex, numChars, str.length());
MemorySegment segment;
if (StringSupport.bytesCompatible(str, charset, srcIndex, numChars)) {
segment = allocateNoInit(numChars);
StringSupport.copyToSegmentRaw(str, segment, 0, srcIndex, numChars);
} else {
byte[] bytes = str.substring(srcIndex, srcIndex + numChars).getBytes(charset);
segment = allocateNoInit(bytes.length);
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
}
return segment;
}
/**
* {@return a new memory segment initialized with the provided byte value}
* <p>

View File

@ -634,10 +634,10 @@ public interface JavaLangAccess {
/**
* Copy the string bytes to an existing segment, avoiding intermediate copies.
*/
void copyToSegmentRaw(String string, MemorySegment segment, long offset);
void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength);
/**
* Are the string bytes compatible with the given charset?
*/
boolean bytesCompatible(String string, Charset charset);
boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars);
}

View File

@ -832,10 +832,8 @@ public abstract sealed class AbstractInstruction
final int slot;
final int constant;
public UnboundIncrementInstruction(int slot, int constant) {
super(BytecodeHelpers.validateAndIsWideIinc(slot, constant)
? Opcode.IINC_W
: Opcode.IINC);
public UnboundIncrementInstruction(Opcode op, int slot, int constant) {
super(op);
this.slot = slot;
this.constant = constant;
}

View File

@ -450,6 +450,13 @@ public class BytecodeHelpers {
return ret;
}
public static void validateIncrement(Opcode opcode, int slot, int constant) {
if (validateAndIsWideIinc(slot, constant) && opcode != Opcode.IINC_W) {
throw new IllegalArgumentException(
"IINC: operands require wide encoding for %s".formatted(opcode));
}
}
public static void validateRet(Opcode opcode, int slot) {
if (opcode == Opcode.RET && (slot & ~0xFF) == 0 ||
opcode == Opcode.RET_W && (slot & ~0xFFFF) == 0)

View File

@ -551,6 +551,13 @@ public abstract sealed class AbstractMemorySegmentImpl
unsafeGetOffset() == that.unsafeGetOffset();
}
@Override
public String getString(long offset, Charset charset, long byteLength) {
Utils.checkNonNegativeArgument(byteLength, "byteLength");
Objects.requireNonNull(charset);
return StringSupport.read(this, offset, charset, byteLength);
}
@Override
public int hashCode() {
return Objects.hash(
@ -702,6 +709,16 @@ public abstract sealed class AbstractMemorySegmentImpl
}
}
@ForceInline
public static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
Objects.requireNonNull(src);
Objects.requireNonNull(dstEncoding);
Objects.requireNonNull(dst);
AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dst;
return StringSupport.copyBytes(src, destImpl, dstEncoding, dstOffset, srcIndex, numChars);
}
// accessors
@ForceInline

View File

@ -30,11 +30,14 @@ import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.ScopedMemoryAccess;
import jdk.internal.util.Architecture;
import jdk.internal.util.ArraysSupport;
import jdk.internal.util.Preconditions;
import jdk.internal.vm.annotation.ForceInline;
import java.lang.foreign.MemorySegment;
import java.lang.reflect.Array;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Objects;
import static java.lang.foreign.ValueLayout.*;
@ -58,6 +61,27 @@ public final class StringSupport {
};
}
@ForceInline
public static String read(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
return readBytes(segment, offset, charset, length);
}
@ForceInline
public static String readBytes(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
if (length > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Required length exceeds implementation limit");
}
final int lengthBytes = (int) length;
final byte[] bytes = new byte[lengthBytes];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, lengthBytes);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
}
@ForceInline
public static void write(AbstractMemorySegmentImpl segment, long offset, Charset charset, String string) {
switch (CharsetKind.of(charset)) {
@ -70,14 +94,7 @@ public final class StringSupport {
@ForceInline
private static String readByte(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
final int len = strlenByte(segment, offset, segment.byteSize());
final byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -89,14 +106,7 @@ public final class StringSupport {
@ForceInline
private static String readShort(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
int len = strlenShort(segment, offset, segment.byteSize());
byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -108,14 +118,7 @@ public final class StringSupport {
@ForceInline
private static String readInt(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
int len = strlenInt(segment, offset, segment.byteSize());
byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -345,22 +348,26 @@ public final class StringSupport {
}
}
public static boolean bytesCompatible(String string, Charset charset) {
return JAVA_LANG_ACCESS.bytesCompatible(string, charset);
public static boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
return JAVA_LANG_ACCESS.bytesCompatible(string, charset, srcIndex, numChars);
}
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) {
if (bytesCompatible(string, charset)) {
copyToSegmentRaw(string, segment, offset);
return string.length();
return copyBytes(string, segment, charset, offset, 0, string.length());
}
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset, int srcIndex, int numChars) {
if (bytesCompatible(string, charset, srcIndex, numChars)) {
copyToSegmentRaw(string, segment, offset, srcIndex, numChars);
return numChars;
} else {
byte[] bytes = string.getBytes(charset);
byte[] bytes = string.substring(srcIndex, srcIndex + numChars).getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
return bytes.length;
}
}
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset);
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset, srcIndex, srcLength);
}
}

View File

@ -162,6 +162,35 @@ TEST_VM(AtomicIntegerTest, cmpxchg_int64) {
Support().test();
}
template<typename T>
struct AtomicIntegerCmpsetTestSupport {
Atomic<T> _test_value;
AtomicIntegerCmpsetTestSupport() : _test_value{} {}
void test() {
T zero = 0;
T five = 5;
T ten = 10;
_test_value.store_relaxed(zero);
EXPECT_FALSE(_test_value.compare_set(five, ten));
EXPECT_EQ(zero, _test_value.load_relaxed());
EXPECT_TRUE(_test_value.compare_set(zero, ten));
EXPECT_EQ(ten, _test_value.load_relaxed());
}
};
TEST_VM(AtomicIntegerTest, cmpset_int32) {
using Support = AtomicIntegerCmpsetTestSupport<int32_t>;
Support().test();
}
TEST_VM(AtomicIntegerTest, cmpset_int64) {
// Check if 64-bit atomics are available on the machine.
using Support = AtomicIntegerCmpsetTestSupport<int64_t>;
Support().test();
}
struct AtomicXchgAndCmpxchg1ByteStressSupport {
char _default_val;
int _base;

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.c2.igvn;
/*
* @test
* @bug 8372302
* @summary ModINode::Ideal and ModLNode::Ideal use an intermediate "hook" node
* to keep stuff alive between phase->transform(...) calls. In some cases,
* this node is not properly deleted before returning, causing failure
* in the verification because the node count has changed. This test
* ensures that the intermediate node gets destroyed before returning.
* @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
* -Xcomp -XX:-TieredCompilation
* -XX:CompileCommand=compileonly,${test.main.class}::test*
* -XX:VerifyIterativeGVN=1110
* ${test.main.class}
* @run main ${test.main.class}
*
*/
public class TestModIdealCreatesUselessNode {
static int test0(int x) {
return x % Integer.MIN_VALUE;
}
static long test1(long x) {
return x % Long.MIN_VALUE;
}
public static void main(String[] args) {
test0(0);
test1(0L);
}
}

View File

@ -274,6 +274,7 @@ public final class Operations {
ops.add(Expression.make(BOOLEANS, "Boolean.logicalXor(", BOOLEANS, ", ", BOOLEANS, ")"));
// TODO: Math and other classes.
// Note: Math.copySign is non-deterministic because of NaN having encoding with sign bit set and unset.
// Make sure the list is not modifiable.
return List.copyOf(ops);
@ -294,7 +295,8 @@ public final class Operations {
ops.add(Expression.make(INTS, "Float16.compare(", FLOAT16, ",", FLOAT16, ")"));
addComparisonOperations(ops, "Float16.compare", FLOAT16);
ops.add(Expression.make(INTS, "(", FLOAT16, ").compareTo(", FLOAT16, ")"));
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")"));
// Note: There are NaN encodings with bit set or unset.
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")", WITH_NONDETERMINISTIC_RESULT));
ops.add(Expression.make(FLOAT16, "Float16.divide(", FLOAT16, ",", FLOAT16, ")"));
ops.add(Expression.make(BOOLEANS, "", FLOAT16, ".equals(", FLOAT16, ")"));
// Note: there are multiple NaN values with different bit representations.

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2026 SAP SE. All rights reserved.
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import jdk.test.lib.Platform;
/*
* @test
* @summary Check for unwanted file (types/extensions) in the jdk image
* @library /test/lib
* @requires !vm.debug
* @run main CheckFiles
*/
public class CheckFiles {
// Set this property on command line to scan an alternate dir or file:
// JTREG=JAVA_OPTIONS=-Djdk.test.build.CheckFiles.dir=/path/to/dir
public static final String DIR_PROPERTY = "jdk.test.build.CheckFiles.dir";
public static void main(String[] args) throws Exception {
String jdkPathString = System.getProperty("test.jdk");
Path jdkHome = Paths.get(jdkPathString);
Path mainDirToScan = jdkHome;
String overrideDir = System.getProperty(DIR_PROPERTY);
if (overrideDir != null) {
mainDirToScan = Paths.get(overrideDir);
}
System.out.println("Main directory to scan:" + mainDirToScan);
Path binDir = mainDirToScan.resolve("bin");
Path libDir = mainDirToScan.resolve("lib");
System.out.println("Bin directory to scan:" + binDir);
ArrayList<String> allowedEndingsBinDir = new ArrayList<>();
// UNIX - no extensions are allowed; Windows : .dll, .exe, .pdb, .jsa
if (Platform.isWindows()) {
allowedEndingsBinDir.add(".dll");
allowedEndingsBinDir.add(".exe");
allowedEndingsBinDir.add(".pdb");
allowedEndingsBinDir.add(".jsa");
}
boolean binDirRes = scanFiles(binDir, allowedEndingsBinDir);
System.out.println("Lib directory to scan:" + libDir);
ArrayList<String> allowedEndingsLibDir = new ArrayList<>();
allowedEndingsLibDir.add(".jfc"); // jfr config files
allowedEndingsLibDir.add("cacerts");
allowedEndingsLibDir.add("blocked.certs");
allowedEndingsLibDir.add("public_suffix_list.dat");
allowedEndingsLibDir.add("classlist");
allowedEndingsLibDir.add("fontconfig.bfc");
allowedEndingsLibDir.add("fontconfig.properties.src");
allowedEndingsLibDir.add("ct.sym");
allowedEndingsLibDir.add("jrt-fs.jar");
allowedEndingsLibDir.add("jvm.cfg");
allowedEndingsLibDir.add("modules");
allowedEndingsLibDir.add("psfontj2d.properties");
allowedEndingsLibDir.add("psfont.properties.ja");
allowedEndingsLibDir.add("src.zip");
allowedEndingsLibDir.add("tzdb.dat");
if (Platform.isWindows()) {
allowedEndingsLibDir.add(".lib");
allowedEndingsLibDir.add("tzmappings");
} else {
allowedEndingsLibDir.add("jexec");
allowedEndingsLibDir.add("jspawnhelper");
allowedEndingsLibDir.add(".jsa");
if (Platform.isOSX()) {
allowedEndingsLibDir.add("shaders.metallib");
allowedEndingsLibDir.add(".dylib");
} else {
allowedEndingsLibDir.add(".so");
}
if (Platform.isAix()) {
allowedEndingsLibDir.add("tzmappings");
}
}
boolean libDirRes = scanFiles(libDir, allowedEndingsLibDir);
if (!binDirRes) {
throw new Error("bin dir scan failed");
}
if (!libDirRes) {
throw new Error("lib dir scan failed");
}
}
private static boolean scanFiles(Path root, ArrayList<String> allowedEndings) throws IOException {
AtomicBoolean badFileFound = new AtomicBoolean(false);
Files.walkFileTree(root, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String fullFileName = file.toString();
String fileName = file.getFileName().toString();
System.out.println(" visiting file:" + fullFileName);
checkFile(fileName, allowedEndings);
return super.visitFile(file, attrs);
}
private void checkFile(String name, ArrayList<String> allowedEndings) {
if (allowedEndings.isEmpty()) { // no file extensions allowed
int lastDot = name.lastIndexOf('.');
if (lastDot > 0) {
System.out.println(" --> ERROR this file is not allowed:" + name);
badFileFound.set(true);
}
} else {
boolean allowed = allowedEndings.stream().anyMatch(name::endsWith);
if (! allowed) {
System.out.println(" --> ERROR this file is not allowed:" + name);
badFileFound.set(true);
}
}
}
});
return !badFileFound.get();
}
}

View File

@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.function.UnaryOperator;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
@ -102,6 +103,140 @@ public class TestStringEncoding {
}
}
@Test(dataProvider = "strings")
public void testStringsLength(String testString) {
if (!testString.isEmpty()) {
for (Charset charset : Charset.availableCharsets().values()) {
if (charset.canEncode()) {
for (Arena arena : arenas()) {
try (arena) {
MemorySegment text = arena.allocateFrom(testString, charset, 0, testString.length());
long length = text.byteSize();
assertEquals(length, testString.getBytes(charset).length);
String roundTrip = text.getString(0, charset, length);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, testString);
}
}
}
}
}
}
}
@Test(dataProvider = "strings")
public void testStringsCopy(String testString) {
if (!testString.isEmpty()) {
for (Charset charset : Charset.availableCharsets().values()) {
if (charset.canEncode()) {
for (Arena arena : arenas()) {
try (arena) {
byte[] bytes = testString.getBytes(charset);
MemorySegment text = arena.allocate(JAVA_BYTE, bytes.length);
MemorySegment.copy(testString, charset, 0, text, 0, testString.length());
String roundTrip = text.getString(0, charset, bytes.length);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, testString);
}
}
}
}
}
}
}
@Test
public void testStringsLengthNegative() {
try (Arena arena = Arena.ofConfined()) {
var segment = arena.allocateFrom("abc");
assertThrows(IllegalArgumentException.class, () -> segment.getString(1, StandardCharsets.UTF_8, -1));
}
}
@Test
public void testCopyThrows() {
try (Arena arena = Arena.ofConfined()) {
String testString = "abc";
String testString_notBytesCompatible = "snowman \u26C4";
MemorySegment text = arena.allocate(JAVA_BYTE, 3);
MemorySegment text_notBytesCompatible = arena.allocate(JAVA_BYTE,
testString_notBytesCompatible.getBytes(StandardCharsets.UTF_8).length);
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, testString.length());
MemorySegment.copy(testString_notBytesCompatible, StandardCharsets.UTF_8, 0,
text_notBytesCompatible, 0,
testString_notBytesCompatible.length());
// srcIndex < 0
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, -1, text, 0, testString.length()));
// dstOffset < 0
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, -1, testString.length()));
// numChars < 0
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, -1));
// srcIndex + numChars > length
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, 1, text, 0, testString.length()));
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 0, testString.length() + 1));
// dstOffset > byteSize() - B
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, 0, text, 1, testString.length()));
// srcIndex + numChars overflows
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString, StandardCharsets.UTF_8, Integer.MAX_VALUE, text, 0, Integer.MAX_VALUE + 3));
assertThrows(IndexOutOfBoundsException.class, () ->
MemorySegment.copy(testString_notBytesCompatible, StandardCharsets.UTF_8, Integer.MAX_VALUE, text, 0, Integer.MAX_VALUE + 3));
}
}
@Test
public void testAllocateFromThrows() {
try (Arena arena = Arena.ofConfined()) {
String testString = "abc";
String testString_notBytesCompatible = "snowman \u26C4";
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length());
arena.allocateFrom(testString, StandardCharsets.UTF_8, 2, 1);
// srcIndex < 0
assertThrows(IndexOutOfBoundsException.class, () ->
arena.allocateFrom(testString, StandardCharsets.UTF_8, -1, testString.length()));
// numChars < 0
assertThrows(IndexOutOfBoundsException.class, () ->
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, -1));
// srcIndex + numChars > length
assertThrows(IndexOutOfBoundsException.class, () ->
arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length() + 1));
assertThrows(IndexOutOfBoundsException.class, () ->
arena.allocateFrom(testString, StandardCharsets.UTF_8, 1, testString.length()));
// srcIndex + numChars overflows
assertThrows(IndexOutOfBoundsException.class, () ->
arena.allocateFrom(testString, StandardCharsets.UTF_8, 3, Integer.MAX_VALUE));
assertThrows(IndexOutOfBoundsException.class, () -> arena.allocateFrom(
testString_notBytesCompatible, StandardCharsets.UTF_8, 3, Integer.MAX_VALUE));
}
}
@Test
public void testGetStringThrows() {
try (Arena arena = Arena.ofConfined()) {
String testString = "abc";
MemorySegment text = arena.allocateFrom(testString, StandardCharsets.UTF_8, 0, testString.length());
text.getString(0, StandardCharsets.UTF_8, 3);
// unsupported string size
assertThrows(IllegalArgumentException.class, () ->
text.getString(0, StandardCharsets.UTF_8, Integer.MAX_VALUE + 1L));
// offset < 0
assertThrows(IndexOutOfBoundsException.class, () ->
text.getString(-1, StandardCharsets.UTF_8, 3));
// offset > byteSize() - length
assertThrows(IndexOutOfBoundsException.class, () ->
text.getString(1, StandardCharsets.UTF_8, 3));
// length < 0
assertThrows(IllegalArgumentException.class, () ->
text.getString(0, StandardCharsets.UTF_8, -1));
}
}
@Test(dataProvider = "strings")
public void testStringsHeap(String testString) {
for (Charset charset : singleByteCharsets()) {
@ -221,6 +356,74 @@ public class TestStringEncoding {
}
}
@Test(dataProvider = "strings")
public void testSubstringGetString(String testString) {
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
return;
}
for (var charset : singleByteCharsets()) {
for (var arena: arenas()) {
try (arena) {
MemorySegment text = arena.allocateFrom(testString, charset, 0, testString.length());
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
// this test assumes single-byte charsets
String roundTrip = text.getString(srcIndex, charset, numChars);
String substring = testString.substring(srcIndex, srcIndex + numChars);
assertEquals(roundTrip, substring);
}
}
}
}
}
}
@Test(dataProvider = "strings")
public void testSubstringAllocate(String testString) {
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
return;
}
for (var charset : singleByteCharsets()) {
for (var arena: arenas()) {
try (arena) {
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
MemorySegment text = arena.allocateFrom(testString, charset, srcIndex, numChars);
String substring = testString.substring(srcIndex, srcIndex + numChars);
assertEquals(text.byteSize(), substring.getBytes(charset).length);
String roundTrip = text.getString(0, charset, text.byteSize());
assertEquals(roundTrip, substring);
}
}
}
}
}
}
@Test(dataProvider = "strings")
public void testSubstringCopy(String testString) {
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
return;
}
for (var charset : singleByteCharsets()) {
for (var arena: arenas()) {
try (arena) {
for (int srcIndex = 0; srcIndex <= testString.length(); srcIndex++) {
for (int numChars = 0; numChars <= testString.length() - srcIndex; numChars++) {
String substring = testString.substring(srcIndex, srcIndex + numChars);
long length = substring.getBytes(charset).length;
MemorySegment text = arena.allocate(JAVA_BYTE, length);
long copied = MemorySegment.copy(testString, charset, srcIndex, text, 0, numChars);
String roundTrip = text.getString(0, charset, length);
assertEquals(roundTrip, substring);
assertEquals(copied, length);
}
}
}
}
}
}
private static final MemoryLayout CHAR_POINTER = ADDRESS
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
private static final Linker LINKER = Linker.nativeLinker();
@ -402,7 +605,7 @@ public class TestStringEncoding {
{""},
{"X"},
{"12345"},
{"yen \u00A5"},
{"section \u00A7"},
{"snowman \u26C4"},
{"rainbow \uD83C\uDF08"},
{"0"},

View File

@ -195,6 +195,7 @@ class InstructionValidationTest {
ensureFailFast(i, cob -> cob.iinc(i, 1));
}
check(fails, () -> IncrementInstruction.of(i, 1));
check(fails, () -> IncrementInstruction.of(IINC_W, i, 1));
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(i));
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(RET_W, i));
check(fails, () -> LocalVariable.of(i, "test", CD_Object, dummyLabel, dummyLabel));
@ -208,6 +209,7 @@ class InstructionValidationTest {
check(fails, () -> LoadInstruction.of(u1Op, i));
for (var u1Op : List.of(ASTORE, ISTORE, LSTORE, FSTORE, DSTORE))
check(fails, () -> StoreInstruction.of(u1Op, i));
check(fails, () -> IncrementInstruction.of(IINC, i, 1));
check(fails, () -> DiscontinuedInstruction.RetInstruction.of(RET, i));
}
@ -250,6 +252,13 @@ class InstructionValidationTest {
IncrementInstruction.of(0, 2);
IncrementInstruction.of(0, Short.MAX_VALUE);
IncrementInstruction.of(0, Short.MIN_VALUE);
IncrementInstruction.of(IINC, 0, 2);
IncrementInstruction.of(IINC, 0, Byte.MIN_VALUE);
IncrementInstruction.of(IINC, 0, Byte.MAX_VALUE);
IncrementInstruction.of(IINC_W, 0, 2);
IncrementInstruction.of(IINC_W, 0, Short.MIN_VALUE);
IncrementInstruction.of(IINC_W, 0, Short.MAX_VALUE);
for (int i : new int[] {Short.MIN_VALUE - 1, Short.MAX_VALUE + 1}) {
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(0, i));
TestUtil.runCodeHandler(cob -> {
@ -257,6 +266,12 @@ class InstructionValidationTest {
cob.return_();
});
}
for (int i : new int[] {Byte.MIN_VALUE - 1, Byte.MAX_VALUE + 1}) {
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(IINC, 0, i));
}
for (int i : new int[] {Short.MIN_VALUE - 1, Short.MAX_VALUE + 1}) {
assertThrows(IllegalArgumentException.class, () -> IncrementInstruction.of(IINC_W, 0, i));
}
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -34,8 +34,9 @@ import jdk.jfr.Configuration;
import jdk.jfr.Event;
import jdk.jfr.Recording;
import jdk.jfr.StackTrace;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.RecordedClassLoader;
import jdk.jfr.consumer.RecordingStream;
import jdk.test.lib.jfr.TestClassLoader;
/**
* @test
@ -52,28 +53,17 @@ public class TestBackToBackSensitive {
static class FillEvent extends Event {
String message;
}
public static Object OBJECT;
public static void main(String... arg) throws Exception {
Set<Instant> threadDumps = Collections.synchronizedSet(new LinkedHashSet<>());
Set<Instant> classLoaderStatistics = Collections.synchronizedSet(new LinkedHashSet<>());
Set<Instant> physicalMemory = Collections.synchronizedSet(new LinkedHashSet<>());
TestClassLoader loader = new TestClassLoader();
Class<?> clazz = loader.loadClass(TestBackToBackSensitive.class.getName());
String classLoaderName = loader.getClass().getName();
OBJECT = clazz.getDeclaredConstructor().newInstance();
Configuration configuration = Configuration.getConfiguration("default");
try (RecordingStream r1 = new RecordingStream(configuration)) {
r1.setMaxSize(Long.MAX_VALUE);
r1.onEvent("jdk.ThreadDump", e -> threadDumps.add(e.getStartTime()));
r1.onEvent("jdk.ClassLoaderStatistics", e -> {
RecordedClassLoader cl = e.getValue("classLoader");
if (cl != null) {
if (cl.getType().getName().contains("PlatformClassLoader")) {
classLoaderStatistics.add(e.getStartTime());
System.out.println("Class loader" + e);
}
}
});
r1.onEvent("jdk.PhysicalMemory", e -> physicalMemory.add(e.getStartTime()));
try (Recording r1 = new Recording(configuration)) {
// Start chunk 1
r1.startAsync();
r1.start();
try (Recording r2 = new Recording()) {
// Start chunk 2
r2.start();
@ -86,6 +76,25 @@ public class TestBackToBackSensitive {
f.commit();
}
r1.stop();
Path file = Path.of("file.jfr");
r1.dump(file);
Set<Instant> threadDumps = new LinkedHashSet<>();
Set<Instant> classLoaderStatistics = new LinkedHashSet<>();
Set<Instant> physicalMemory = new LinkedHashSet<>();
try (EventStream es = EventStream.openFile(file)) {
es.onEvent("jdk.ThreadDump", e -> threadDumps.add(e.getStartTime()));
es.onEvent("jdk.ClassLoaderStatistics", e -> {
RecordedClassLoader cl = e.getValue("classLoader");
if (cl != null) {
if (cl.getType().getName().equals(classLoaderName)) {
classLoaderStatistics.add(e.getStartTime());
System.out.println("Class loader" + e);
}
}
});
es.onEvent("jdk.PhysicalMemory", e -> physicalMemory.add(e.getStartTime()));
es.start();
}
long chunkFiles = filesInRepository();
System.out.println("Number of chunk files: " + chunkFiles);
// When jdk.PhysicalMemory is expected to be emitted:
@ -93,15 +102,15 @@ public class TestBackToBackSensitive {
// Chunk 2: begin, end
// Chunk 3: begin, end
// Chunk 4: begin, end
assertCount(r1, "jdk.PhysicalMemory", physicalMemory, 2 * chunkFiles);
assertCount("jdk.PhysicalMemory", physicalMemory, 2 * chunkFiles);
// When jdk.ClassLoaderStatistics and jdk.ThreadThreadDump are expected to be
// emitted:
// Chunk 1: begin, end
// Chunk 2: begin, end
// Chunk 3: end
// Chunk 4: end
assertCount(r1, "jdk.ThreadDump", threadDumps, 2 + 2 + (chunkFiles - 2));
assertCount(r1, "jdk.ClassLoaderStatistics", classLoaderStatistics, 2 + 2 + (chunkFiles - 2));
assertCount("jdk.ThreadDump", threadDumps, 2 + 2 + (chunkFiles - 2));
assertCount("jdk.ClassLoaderStatistics", classLoaderStatistics, 2 + 2 + (chunkFiles - 2));
}
}
@ -110,15 +119,13 @@ public class TestBackToBackSensitive {
return Files.list(repository).filter(p -> p.toString().endsWith(".jfr")).count();
}
private static void assertCount(RecordingStream stream, String eventName, Set<Instant> timestamps, long expected) throws Exception {
private static void assertCount(String eventName, Set<Instant> timestamps, long expected) throws Exception {
System.out.println("Timestamps for " + eventName + ":");
for (Instant timestamp : timestamps) {
System.out.println(timestamp);
}
int count = timestamps.size();
if (count != expected) {
System.out.println("Dumping failure file.");
stream.dump(Path.of("failure.jfr"));
throw new Exception("Expected " + expected + " timestamps for event " + eventName + ", but got " + count);
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3)
public class FromJavaStringTest {
private String str;
private MemorySegment strSegment;
private int lengthBytes;
@Param({"5", "20", "100", "200", "451"})
int size;
@Setup
public void setup() {
var arena = Arena.ofAuto();
while (LOREM.length() < size) {
LOREM += LOREM;
}
str = LOREM.substring(0, size);
strSegment = arena.allocateFrom(str);
lengthBytes = str.getBytes(UTF_8).length;
}
@Benchmark
public void segment_setString() {
strSegment.setString(0, str, UTF_8);
}
@Benchmark
public void segment_copyStringRaw() {
MemorySegment.copy(str, UTF_8, 0, strSegment, 0, str.length());
}
@Benchmark
public void segment_copyStringBytes() {
byte[] bytes = str.getBytes(UTF_8);
MemorySegment.copy(bytes, 0, strSegment, JAVA_BYTE, 0, bytes.length);
}
static String LOREM =
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
""";
}

View File

@ -22,6 +22,9 @@
*/
package org.openjdk.bench.java.lang.foreign;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@ -47,6 +50,7 @@ import java.util.concurrent.TimeUnit;
public class ToJavaStringTest {
private MemorySegment strSegment;
private int length;
@Param({"5", "20", "100", "200", "451"})
int size;
@ -61,19 +65,33 @@ public class ToJavaStringTest {
while (LOREM.length() < size) {
LOREM += LOREM;
}
strSegment = arena.allocateFrom(LOREM.substring(0, size));
var s = LOREM.substring(0, size);
strSegment = arena.allocateFrom(s);
length = s.getBytes(UTF_8).length;
}
@Benchmark
public String panama_readString() {
public String segment_getString() {
return strSegment.getString(0);
}
@Benchmark
public String segment_getStringLength() {
return strSegment.getString(0, UTF_8, length);
}
@Benchmark
public String jni_readString() {
return readString(strSegment.address());
}
@Benchmark
public String segment_copyStringBytes() {
byte[] bytes = new byte[length];
MemorySegment.copy(strSegment, JAVA_BYTE, 0, bytes, 0, length);
return new String(bytes, UTF_8);
}
static native String readString(long addr);
static String LOREM = """