From c58ad8315c904e55d34ad37ea36934c1ca2c158d Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 31 Aug 2026 09:48:50 +0200 Subject: [PATCH] wip: snapshot life bitpacked optimization Preserve the in-progress native unsigned bitwise fast path before profiling the closure benchmark. Generated with [Codex](https://openai.com/codex/) Co-Authored-By: Codex --- .../runtime/operators/BitwiseOperators.java | 32 ++++++++++++++++--- .../unit/bitwise_native_word_fastpath.t | 17 ++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/unit/bitwise_native_word_fastpath.t diff --git a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java index 9125ab0274..794fd54511 100644 --- a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java @@ -25,7 +25,13 @@ private static RuntimeScalar unsignedResult(BigInteger value) { } private static boolean hasNativeInteger(RuntimeScalar scalar) { - return scalar.type == RuntimeScalarType.INTEGER && !(scalar.value instanceof BigInteger); + // Perl's ordinary bitwise operators preserve an unsigned 64-bit result. + // Values with the high bit set are consequently stored as BigInteger so + // their decimal representation remains positive. They nevertheless + // contain exactly one machine word, and BigInteger.longValue() retains + // those bits. Keeping them on the native path avoids allocating several + // BigIntegers for every operation in bit-packed workloads. + return scalar.type == RuntimeScalarType.INTEGER && scalar.value instanceof Number; } private static RuntimeScalar unsignedResult(long value) { @@ -56,6 +62,16 @@ private static RuntimeScalar unsignedShiftRight(BigInteger value, long shift) { return unsignedResult(value.shiftRight((int) shift)); } + private static RuntimeScalar unsignedShiftLeft(long value, long shift) { + if (shift >= 64) return RuntimeScalarCache.scalarZero; + return unsignedResult(value << (int) shift); + } + + private static RuntimeScalar unsignedShiftRight(long value, long shift) { + if (shift >= 64) return RuntimeScalarCache.scalarZero; + return unsignedResult(value >>> (int) shift); + } + private static BigInteger exactInteger(RuntimeScalar scalar) { return scalar.type == RuntimeScalarType.INTEGER && scalar.value instanceof BigInteger ? (BigInteger) scalar.value : null; @@ -352,6 +368,10 @@ public static RuntimeScalar bitwiseNot(RuntimeScalar runtimeScalar) { * @return A new RuntimeScalar with the result of the bitwise NOT operation. */ public static RuntimeScalar bitwiseNotBinary(RuntimeScalar runtimeScalar) { + if (hasNativeInteger(runtimeScalar)) { + return unsignedResult(~((Number) runtimeScalar.value).longValue()) + .propagateTaint(runtimeScalar); + } return unsignedResult(unsignedValue(runtimeScalar).xor(UV_MASK)) .propagateTaint(runtimeScalar); } @@ -526,10 +546,11 @@ public static RuntimeScalar shiftLeft(RuntimeScalar runtimeScalar, RuntimeScalar if (t1 == RuntimeScalarType.INTEGER && t2 == RuntimeScalarType.INTEGER && exactInteger(arg2) == null) { long shift = arg2.getLong(); + long value = runtimeScalar.getLong(); if (shift >= 0) { - return unsignedShiftLeft(unsignedValue(runtimeScalar), shift); + return unsignedShiftLeft(value, shift); } else if (shift != Long.MIN_VALUE) { - return unsignedShiftRight(unsignedValue(runtimeScalar), -shift); + return unsignedShiftRight(value, -shift); } return RuntimeScalarCache.scalarZero; } @@ -615,10 +636,11 @@ public static RuntimeScalar shiftRight(RuntimeScalar runtimeScalar, RuntimeScala if (t1 == RuntimeScalarType.INTEGER && t2 == RuntimeScalarType.INTEGER && exactInteger(arg2) == null) { long shift = arg2.getLong(); + long value = runtimeScalar.getLong(); if (shift >= 0) { - return unsignedShiftRight(unsignedValue(runtimeScalar), shift); + return unsignedShiftRight(value, shift); } else if (shift != Long.MIN_VALUE) { - return unsignedShiftLeft(unsignedValue(runtimeScalar), -shift); + return unsignedShiftLeft(value, -shift); } return RuntimeScalarCache.scalarZero; } diff --git a/src/test/resources/unit/bitwise_native_word_fastpath.t b/src/test/resources/unit/bitwise_native_word_fastpath.t new file mode 100644 index 0000000000..3dd5ea56a3 --- /dev/null +++ b/src/test/resources/unit/bitwise_native_word_fastpath.t @@ -0,0 +1,17 @@ +use strict; +use warnings; + +use Test::More tests => 4; + +# Ordinary numeric bitwise operations keep an unsigned result when bit 63 is +# set. Reusing that result must preserve the low word through native bitwise +# and shift operations. +my $all_bits = ~0; +is(($all_bits & 0xffff_ffff), 4_294_967_295, + 'AND accepts an unsigned word result'); +is(($all_bits ^ $all_bits), 0, + 'XOR accepts an unsigned word result'); +is(($all_bits >> 32), 4_294_967_295, + 'right shift accepts an unsigned word result'); +is((($all_bits << 1) | 1), '18446744073709551615', + 'chained operations retain unsigned word semantics');