Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1932,9 +1932,12 @@ void handleCompoundAssignment(BinaryOperatorNode node) {
case "/=" -> emit(isIntegerEnabled() ? Opcodes.INTEGER_DIV_ASSIGN : Opcodes.DIVIDE_ASSIGN);
case "%=" -> emit(isIntegerEnabled() ? Opcodes.INTEGER_MOD_ASSIGN : Opcodes.MODULUS_ASSIGN);
case ".=" -> emit(Opcodes.STRING_CONCAT_ASSIGN);
case "&=", "binary&=" -> emit(Opcodes.BITWISE_AND_ASSIGN); // Numeric bitwise AND
case "|=", "binary|=" -> emit(Opcodes.BITWISE_OR_ASSIGN); // Numeric bitwise OR
case "^=", "binary^=" -> emit(Opcodes.BITWISE_XOR_ASSIGN); // Numeric bitwise XOR
case "&=" -> emit(Opcodes.BITWISE_AND_ASSIGN); // Bitwise AND (dispatch)
case "|=" -> emit(Opcodes.BITWISE_OR_ASSIGN); // Bitwise OR (dispatch)
case "^=" -> emit(Opcodes.BITWISE_XOR_ASSIGN); // Bitwise XOR (dispatch)
case "binary&=" -> emit(Opcodes.BINARY_AND_ASSIGN); // Numeric-only bitwise AND
case "binary|=" -> emit(Opcodes.BINARY_OR_ASSIGN); // Numeric-only bitwise OR
case "binary^=" -> emit(Opcodes.BINARY_XOR_ASSIGN); // Numeric-only bitwise XOR
case "&.=" -> emit(Opcodes.STRING_BITWISE_AND_ASSIGN); // String bitwise AND
case "|.=" -> emit(Opcodes.STRING_BITWISE_OR_ASSIGN); // String bitwise OR
case "^.=" -> emit(Opcodes.STRING_BITWISE_XOR_ASSIGN); // String bitwise XOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,24 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
pc = OpcodeHandlerExtended.executeStringBitwiseXorAssign(bytecode, pc, registers);
}

case Opcodes.BINARY_AND_ASSIGN -> {
// Numeric-only bitwise AND assign (use feature "bitwise")
// Format: BINARY_AND_ASSIGN rd rs
pc = OpcodeHandlerExtended.executeBinaryAndAssign(bytecode, pc, registers);
}

case Opcodes.BINARY_OR_ASSIGN -> {
// Numeric-only bitwise OR assign (use feature "bitwise")
// Format: BINARY_OR_ASSIGN rd rs
pc = OpcodeHandlerExtended.executeBinaryOrAssign(bytecode, pc, registers);
}

case Opcodes.BINARY_XOR_ASSIGN -> {
// Numeric-only bitwise XOR assign (use feature "bitwise")
// Format: BINARY_XOR_ASSIGN rd rs
pc = OpcodeHandlerExtended.executeBinaryXorAssign(bytecode, pc, registers);
}

case Opcodes.BITWISE_AND_BINARY -> {
// Numeric bitwise AND: rd = rs1 binary& rs2
// Format: BITWISE_AND_BINARY rd rs1 rs2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(rs2);
}
case "binary&" -> {
// Numeric bitwise AND (use integer): rs1 binary& rs2
// Same as & but explicitly numeric
bytecodeCompiler.emit(Opcodes.BITWISE_AND_BINARY);
// Numeric bitwise AND (use feature "bitwise"): always numeric
bytecodeCompiler.emit(Opcodes.BINARY_AND);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
Expand All @@ -373,9 +372,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(rs2);
}
case "binary|" -> {
// Numeric bitwise OR (use integer): rs1 binary| rs2
// Same as | but explicitly numeric
bytecodeCompiler.emit(Opcodes.BITWISE_OR_BINARY);
// Numeric bitwise OR (use feature "bitwise"): always numeric
bytecodeCompiler.emit(Opcodes.BINARY_OR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
Expand All @@ -388,9 +386,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(rs2);
}
case "binary^" -> {
// Numeric bitwise XOR (use integer): rs1 binary^ rs2
// Same as ^ but explicitly numeric
bytecodeCompiler.emit(Opcodes.BITWISE_XOR_BINARY);
// Numeric bitwise XOR (use feature "bitwise"): always numeric
bytecodeCompiler.emit(Opcodes.BINARY_XOR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,21 @@ public static String disassemble(InterpretedCode interpretedCode) {
rs = interpretedCode.bytecode[pc++];
sb.append("STRING_BITWISE_XOR_ASSIGN r").append(rd).append(" ^.= r").append(rs).append("\n");
break;
case Opcodes.BINARY_AND_ASSIGN:
rd = interpretedCode.bytecode[pc++];
rs = interpretedCode.bytecode[pc++];
sb.append("BINARY_AND_ASSIGN r").append(rd).append(" binary&= r").append(rs).append("\n");
break;
case Opcodes.BINARY_OR_ASSIGN:
rd = interpretedCode.bytecode[pc++];
rs = interpretedCode.bytecode[pc++];
sb.append("BINARY_OR_ASSIGN r").append(rd).append(" binary|= r").append(rs).append("\n");
break;
case Opcodes.BINARY_XOR_ASSIGN:
rd = interpretedCode.bytecode[pc++];
rs = interpretedCode.bytecode[pc++];
sb.append("BINARY_XOR_ASSIGN r").append(rd).append(" binary^= r").append(rs).append("\n");
break;
case Opcodes.BITWISE_AND_BINARY:
rd = interpretedCode.bytecode[pc++];
int andRs1 = interpretedCode.bytecode[pc++];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,4 +1073,58 @@ public static int executeModulusAssign(int[] bytecode, int pc, RuntimeBase[] reg
registers[rd] = MathOperators.modulusAssignWarn(s1, s2);
return pc;
}

/**
* Execute numeric-only bitwise AND assign (use feature "bitwise").
* Format: BINARY_AND_ASSIGN rd rs
*/
public static int executeBinaryAndAssign(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (BytecodeInterpreter.isImmutableProxy(registers[rd])) {
registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]);
}
RuntimeScalar result = BitwiseOperators.bitwiseAndBinary(
(RuntimeScalar) registers[rd],
(RuntimeScalar) registers[rs]
);
((RuntimeScalar) registers[rd]).set(result);
return pc;
}

/**
* Execute numeric-only bitwise OR assign (use feature "bitwise").
* Format: BINARY_OR_ASSIGN rd rs
*/
public static int executeBinaryOrAssign(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (BytecodeInterpreter.isImmutableProxy(registers[rd])) {
registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]);
}
RuntimeScalar result = BitwiseOperators.bitwiseOrBinary(
(RuntimeScalar) registers[rd],
(RuntimeScalar) registers[rs]
);
((RuntimeScalar) registers[rd]).set(result);
return pc;
}

/**
* Execute numeric-only bitwise XOR assign (use feature "bitwise").
* Format: BINARY_XOR_ASSIGN rd rs
*/
public static int executeBinaryXorAssign(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (BytecodeInterpreter.isImmutableProxy(registers[rd])) {
registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]);
}
RuntimeScalar result = BitwiseOperators.bitwiseXorBinary(
(RuntimeScalar) registers[rd],
(RuntimeScalar) registers[rs]
);
((RuntimeScalar) registers[rd]).set(result);
return pc;
}
}
18 changes: 18 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,24 @@ public class Opcodes {
*/
public static final short SCOPE_EXIT_CLEANUP_ARRAY = 467;

/**
* Numeric-only bitwise AND assign (use feature "bitwise"): rd &= rs (always numeric).
* Format: BINARY_AND_ASSIGN rd rs
*/
public static final short BINARY_AND_ASSIGN = 468;

/**
* Numeric-only bitwise OR assign (use feature "bitwise"): rd |= rs (always numeric).
* Format: BINARY_OR_ASSIGN rd rs
*/
public static final short BINARY_OR_ASSIGN = 469;

/**
* Numeric-only bitwise XOR assign (use feature "bitwise"): rd ^= rs (always numeric).
* Format: BINARY_XOR_ASSIGN rd rs
*/
public static final short BINARY_XOR_ASSIGN = 470;

private Opcodes() {
} // Utility class - no instantiation
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) {
// - Marked as package (true), OR
// - Unknown (null) but NOT followed by '(' - like 'new NonExistentClass'
if ((isPackage != null && !isPackage)
|| (isPackage == null && !isKnownSub && token.text.equals("(") && !packageName.contains("::"))
|| (isPackage == null && !isKnownSub && token.text.equals("(") && !packageName.contains("::") && subExists)
|| (subExists && packageName.contains("::") && token.text.equals("(")
&& !(isPackage != null && isPackage))) {
parser.tokenIndex = currentIndex2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,16 @@ public static RuntimeScalar bitwiseAnd(RuntimeScalar runtimeScalar, RuntimeScala
RuntimeScalarCache.scalarEmptyString, "uninitialized");
}

// In Perl, if either operand is a reference or doesn't look like a number, use string operations
if (!ScalarUtils.looksLikeNumber(val1) || !ScalarUtils.looksLikeNumber(val2)) {
return bitwiseAndDot(val1, val2);
}
return bitwiseAndBinary(val1, val2);
// In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp):
// - If either operand has a numeric type (IOK/NOK), use numeric bitwise
// - If both are non-numeric (strings from pack/vec, etc.), use string bitwise
int vt1 = val1.type;
int vt2 = val2.type;
if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE ||
vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) {
return bitwiseAndBinary(val1, val2);
}
return bitwiseAndDot(val1, val2);
}

/**
Expand Down Expand Up @@ -115,11 +120,16 @@ public static RuntimeScalar bitwiseOr(RuntimeScalar runtimeScalar, RuntimeScalar
t2 == RuntimeScalarType.TIED_SCALAR ? arg2.tiedFetch() :
t2 == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) arg2.value : arg2;

// In Perl, if either operand is a reference or doesn't look like a number, use string operations
if (!ScalarUtils.looksLikeNumber(val1) || !ScalarUtils.looksLikeNumber(val2)) {
return bitwiseOrDot(val1, val2);
// In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp):
// - If either operand has a numeric type (IOK/NOK), use numeric bitwise
// - If both are non-numeric (strings from pack/vec, etc.), use string bitwise
int vt1 = val1.type;
int vt2 = val2.type;
if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE ||
vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) {
return bitwiseOrBinary(val1, val2);
}
return bitwiseOrBinary(val1, val2);
return bitwiseOrDot(val1, val2);
}

/**
Expand Down Expand Up @@ -178,13 +188,15 @@ public static RuntimeScalar bitwiseXor(RuntimeScalar runtimeScalar, RuntimeScala
t2 == RuntimeScalarType.TIED_SCALAR ? arg2.tiedFetch() :
t2 == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) arg2.value : arg2;

// Use numeric XOR only if BOTH operands look like numbers
// For everything else (strings, blessed objects, references, etc.), use string XOR
if (ScalarUtils.looksLikeNumber(val1) && ScalarUtils.looksLikeNumber(val2)) {
// Both are pure numbers (INTEGER or DOUBLE), use numeric XOR
// In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp):
// - If either operand has a numeric type (IOK/NOK), use numeric bitwise
// - If both are non-numeric (strings from pack/vec, etc.), use string bitwise
int vt1 = val1.type;
int vt2 = val2.type;
if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE ||
vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) {
return bitwiseXorBinary(val1, val2);
}
// At least one is a string, reference, or blessed object, use string XOR
return bitwiseXorDot(val1, val2);
}

Expand Down Expand Up @@ -228,11 +240,14 @@ public static RuntimeScalar bitwiseNot(RuntimeScalar runtimeScalar) {
runtimeScalar.type == RuntimeScalarType.TIED_SCALAR ? runtimeScalar.tiedFetch() :
runtimeScalar.type == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) runtimeScalar.value : runtimeScalar;

// In Perl, if the operand is a reference or doesn't look like a number, use string operations
if (!ScalarUtils.looksLikeNumber(val)) {
return bitwiseNotDot(val);
// In Perl, ~$val dispatches based on internal type flags (SvNIOKp):
// - If the operand has a numeric type (IOK/NOK), use numeric NOT
// - If it's a string, use string NOT (character-by-character)
int vt = val.type;
if (vt == RuntimeScalarType.INTEGER || vt == RuntimeScalarType.DOUBLE) {
return bitwiseNotBinary(val);
}
return bitwiseNotBinary(val);
return bitwiseNotDot(val);
}

/**
Expand Down Expand Up @@ -278,8 +293,11 @@ public static RuntimeScalar integerBitwiseNot(RuntimeScalar runtimeScalar) {
runtimeScalar.type == RuntimeScalarType.TIED_SCALAR ? runtimeScalar.tiedFetch() :
runtimeScalar.type == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) runtimeScalar.value : runtimeScalar;

// In Perl, if the operand is a reference or doesn't look like a number, use string operations
if (!ScalarUtils.looksLikeNumber(val)) {
// In Perl, ~$val dispatches based on internal type flags (SvNIOKp):
// - If the operand has a numeric type (IOK/NOK), use numeric NOT
// - If it's a string, use string NOT (character-by-character)
int vt = val.type;
if (vt != RuntimeScalarType.INTEGER && vt != RuntimeScalarType.DOUBLE) {
return bitwiseNotDot(val);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ public static void writeBitString(PackBuffer output, String str, int count, char
int bitIndex = 0;
int byteValue = 0;
int bitsToProcess = Math.min(str.length(), count);
int bytesWritten = 0;

for (int i = 0; i < bitsToProcess; i++) {
char c = str.charAt(i);
Expand All @@ -583,12 +584,19 @@ public static void writeBitString(PackBuffer output, String str, int count, char
bitIndex++;
if (bitIndex == 8) {
output.write(byteValue);
bytesWritten++;
bitIndex = 0;
byteValue = 0;
}
}
if (bitIndex > 0) {
output.write(byteValue);
bytesWritten++;
}
// Zero-pad to fill the requested count (Perl zero-fills to ceil(count/8) bytes)
int totalBytes = (count + 7) / 8;
for (int i = bytesWritten; i < totalBytes; i++) {
output.write(0);
}
}
}
Loading