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
2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Release history of PerlOnJava. See [Roadmap](roadmap.md) for future plans.

## Work in progress

- Fix format declarations being discarded during compilation, unblocking `write` execution.

- Clear weakened references after a nested method releases its final
array-slot owner, restoring `Algorithm::SlidingWindow` eviction and clear
behavior on both execution backends.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7296,9 +7296,13 @@ public void visit(CompilerFlagNode node) {

@Override
public void visit(FormatNode node) {
// Format declarations are handled at the JVM compilation stage.
// When the interpreter backend processes the AST, formats are already
// registered, so this is a no-op.
// Format declarations are compile-time side effects. Keep the parsed
// RuntimeFormat in the constant pool and register it when this code
// executes, before a later write() can look it up.
RuntimeFormat format = new RuntimeFormat(node.formatName);
format.setCompiledLines(node.templateLines);
emit(Opcodes.REGISTER_FORMAT);
emit(addToConstantPool(format));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,12 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
registers[rd] = (RuntimeBase) code.constants[constIndex];
}

case Opcodes.REGISTER_FORMAT -> {
int constIndex = bytecode[pc++];
RuntimeFormat format = (RuntimeFormat) code.constants[constIndex];
GlobalVariable.setGlobalFormatRef(format.formatName, format);
}

case Opcodes.LOAD_INT -> {
// Load integer: rd = immediate (create NEW mutable scalar, not cached)
int rd = bytecode[pc++];
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2534,6 +2534,9 @@ public class Opcodes {
/** Resolve a direct named call with a call-site CV cache. Format: rd nameStringIdx cacheConstIdx. */
public static final short DIRECT_NAMED_CODE_CALL = 535;

/** Register a format declaration from a constant RuntimeFormat. Format: REGISTER_FORMAT constantIdx. */
public static final short REGISTER_FORMAT = 536;

/** Return the mutable {@code $#array} cell. Format: ARRAY_LAST_INDEX_LVALUE rd arrayReg. */
public static final short ARRAY_LAST_INDEX_LVALUE = 518;

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/perlonjava/backend/jvm/EmitBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
Node elem = list.get(i);
if (elem != null
&& !(elem instanceof CompilerFlagNode)
&& !(elem instanceof FormatNode)
&& !(elem instanceof AbstractNode ab && (ab.getBooleanAnnotation("compileTimeOnly") || ab.getBooleanAnnotation("noReturnValue")))) {
lastNonNullIndex = i;
break;
Expand Down Expand Up @@ -306,6 +305,10 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
Object resultRegObj = node.getAnnotation("resultRegister");
int resultReg = (resultRegObj instanceof Integer) ? (Integer) resultRegObj : -1;

// Format declarations have no runtime value, but their registration
// is a required compile-time side effect. Visit them in VOID
// context so write FILEHANDLE can find the declared format.
// EmitFormat itself discards the resulting RuntimeFormat value.
// Emit the statement with current context
if (i == lastNonNullIndex) {
// Special case for the last element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ public void visit(FormatLine node) {

@Override
public void visit(FormatNode node) {
// Default implementation - no action needed for format nodes
// Format declarations are side-effecting nodes. Preserve them while
// folding their enclosing block so the backend can register the
// declaration before a later write() executes.
result = node;
isConstant = false;
}

@Override
Expand Down
15 changes: 15 additions & 0 deletions src/test/resources/unit/format_write_regression.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use strict;
use warnings;
use Test::More tests => 1;

format STDOUT =
.

my $error;
{
local $@;
eval { write };
$error = $@;
}

ok !$error, 'write registers and executes a declared format';
Loading