From 6d162cd4156cb0995f1fda95b2b0249dc153fc68 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Thu, 19 Feb 2026 17:01:45 +0900 Subject: [PATCH] feat(translate): implement EXIT word and fix UNLOOP for early loop exit --- lib/Translation/ForthToMLIR/ForthToMLIR.cpp | 33 ++++++++++++++++++- test/Pipeline/exit.forth | 6 ++++ test/Pipeline/unloop-exit.forth | 6 ++++ .../Forth/exit-outside-word-error.forth | 3 ++ test/Translation/Forth/exit.forth | 13 ++++++++ test/Translation/Forth/unloop-exit.forth | 21 ++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 test/Pipeline/exit.forth create mode 100644 test/Pipeline/unloop-exit.forth create mode 100644 test/Translation/Forth/exit-outside-word-error.forth create mode 100644 test/Translation/Forth/exit.forth create mode 100644 test/Translation/Forth/unloop-exit.forth diff --git a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp index 0bf746f..edbe11f 100644 --- a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp +++ b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp @@ -588,7 +588,38 @@ LogicalResult ForthParser::parseBody(Value &stack) { return emitError("UNLOOP without matching DO"); } - (void)loopStack.pop_back_val(); + // No-op: loop control uses CFG blocks and a memref counter, not the + // Forth stack, so there is nothing to discard. We keep the loopStack + // intact so that LOOP can still find its matching DO. + + //=== EXIT === + } else if (word == "EXIT") { + consume(); + + if (!inWordDefinition) { + return emitError("EXIT outside word definition"); + } + + Region *parentRegion = builder.getInsertionBlock()->getParent(); + + // Create a block that performs the return. + auto *returnBlock = createStackBlock(parentRegion, loc); + { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(returnBlock); + builder.create(loc, returnBlock->getArgument(0)); + } + + // Use a dummy cond_br to keep the dead block structurally reachable, + // matching the pattern used by LEAVE. + auto *deadBlock = createStackBlock(parentRegion, loc); + Value cond = builder.create( + loc, builder.getI1Type(), builder.getBoolAttr(true)); + builder.create(loc, cond, returnBlock, + ValueRange{stack}, deadBlock, + ValueRange{stack}); + builder.setInsertionPointToStart(deadBlock); + stack = deadBlock->getArgument(0); //=== DO === } else if (word == "DO") { diff --git a/test/Pipeline/exit.forth b/test/Pipeline/exit.forth new file mode 100644 index 0000000..4a29b93 --- /dev/null +++ b/test/Pipeline/exit.forth @@ -0,0 +1,6 @@ +\ RUN: %warpforth-translate --forth-to-mlir %s | %warpforth-opt --warpforth-pipeline | %FileCheck %s +\ CHECK: gpu.binary @warpforth_module + +PARAM DATA 4 +: DO-EXIT 1 IF EXIT THEN 42 ; +DO-EXIT DATA 0 CELLS + ! diff --git a/test/Pipeline/unloop-exit.forth b/test/Pipeline/unloop-exit.forth new file mode 100644 index 0000000..65bbc50 --- /dev/null +++ b/test/Pipeline/unloop-exit.forth @@ -0,0 +1,6 @@ +\ RUN: %warpforth-translate --forth-to-mlir %s | %warpforth-opt --warpforth-pipeline | %FileCheck %s +\ CHECK: gpu.binary @warpforth_module + +PARAM DATA 4 +: FIND-FIVE 10 0 DO I 5 = IF UNLOOP EXIT THEN LOOP 0 ; +FIND-FIVE DATA 0 CELLS + ! diff --git a/test/Translation/Forth/exit-outside-word-error.forth b/test/Translation/Forth/exit-outside-word-error.forth new file mode 100644 index 0000000..07d7fe9 --- /dev/null +++ b/test/Translation/Forth/exit-outside-word-error.forth @@ -0,0 +1,3 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: EXIT outside word definition +EXIT diff --git a/test/Translation/Forth/exit.forth b/test/Translation/Forth/exit.forth new file mode 100644 index 0000000..dae8fe9 --- /dev/null +++ b/test/Translation/Forth/exit.forth @@ -0,0 +1,13 @@ +\ RUN: %warpforth-translate --forth-to-mlir %s | %FileCheck %s + +\ CHECK: func.func private @EARLY_EXIT(%[[A:.*]]: !forth.stack) -> !forth.stack +\ CHECK: cf.cond_br %{{.*}}, ^[[THEN:bb.*]](%{{.*}}), ^[[JOIN:bb.*]](%{{.*}}) +\ CHECK: ^[[THEN]](%[[T:.*]]: !forth.stack): +\ CHECK: cf.cond_br %true, ^[[RET:bb.*]](%[[T]]{{.*}}), ^[[DEAD:bb.*]](%[[T]] +\ CHECK: ^[[JOIN]](%{{.*}}: !forth.stack): +\ CHECK: return %{{.*}} : !forth.stack +\ CHECK: ^[[RET]](%[[R:.*]]: !forth.stack): +\ CHECK: return %[[R]] : !forth.stack + +: EARLY-EXIT 1 IF EXIT THEN 42 ; +EARLY-EXIT diff --git a/test/Translation/Forth/unloop-exit.forth b/test/Translation/Forth/unloop-exit.forth new file mode 100644 index 0000000..1718718 --- /dev/null +++ b/test/Translation/Forth/unloop-exit.forth @@ -0,0 +1,21 @@ +\ RUN: %warpforth-translate --forth-to-mlir %s | %FileCheck %s + +\ Verify the UNLOOP EXIT idiom: early return from a DO LOOP inside a word. + +\ CHECK: func.func private @FIND_FIVE(%{{.*}}: !forth.stack) -> !forth.stack +\ CHECK: memref.alloca +\ CHECK: cf.br ^bb[[#CHECK:]] +\ CHECK: ^bb[[#CHECK]](%{{.*}}: !forth.stack): +\ CHECK: cf.cond_br %{{.*}}, ^bb[[#BODY:]](%{{.*}}), ^bb[[#EXIT:]](%{{.*}}) +\ CHECK: ^bb[[#BODY]](%{{.*}}: !forth.stack): +\ CHECK: forth.eq +\ CHECK: cf.cond_br %{{.*}}, ^bb[[#THEN:]](%{{.*}}), ^bb[[#ENDIF:]](%{{.*}}) +\ CHECK: ^bb[[#EXIT]](%{{.*}}: !forth.stack): +\ CHECK: return +\ CHECK: ^bb[[#THEN]](%[[T:.*]]: !forth.stack): +\ CHECK: cf.cond_br %true, ^bb[[#RET:]](%[[T]]{{.*}}) +\ CHECK: ^bb[[#RET]](%[[R:.*]]: !forth.stack): +\ CHECK: return %[[R]] : !forth.stack + +: FIND-FIVE 10 0 DO I 5 = IF UNLOOP EXIT THEN LOOP 0 ; +FIND-FIVE