From 556af50f83266203c3f291a584aaef10ec26edd6 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 30 Aug 2015 22:15:37 -0700 Subject: [PATCH] Revise low level fiber semantics to play nicer with schedulers. Now that I'm starting to write a real async scheduler on top of Wren's basic fiber API, I have a better feel for what it needs. It turns out run() is not it. - Remove run() methods. - Add transfer() which leaves the caller of the invoked fiber alone. - Add suspend() to return control to the host application. - Add Timer.schedule() to start a new independently scheduled fiber. - Change Timer.sleep() so that it only transfers control to explicitly scheduled fibers, not any one. --- doc/site/core/fiber.markdown | 12 +- doc/site/fibers.markdown | 21 +-- project/xcode/wren.xcodeproj/project.pbxproj | 111 +++---------- script/test.py | 2 +- src/module/timer.c | 26 +++- src/module/timer.wren | 24 ++- src/vm/wren_core.c | 146 +++++++++--------- src/vm/wren_vm.c | 17 +- .../{call_when_done.wren => call_done.wren} | 0 test/core/fiber/call_error.wren | 7 + .../core/fiber/call_return_implicit_null.wren | 5 +- test/core/fiber/call_return_value.wren | 5 +- test/core/fiber/call_transferred.wren | 12 ++ ...en_done.wren => call_with_value_done.wren} | 0 test/core/fiber/call_with_value_error.wren | 7 + .../call_with_value_indirect_reenter.wren | 13 +- .../fiber/call_with_value_transferred.wren | 12 ++ test/core/fiber/run.wren | 9 -- test/core/fiber/run_direct_reenter.wren | 10 -- test/core/fiber/run_indirect_reenter.wren | 20 --- test/core/fiber/run_when_done.wren | 13 -- test/core/fiber/run_with_value.wren | 11 -- .../fiber/run_with_value_direct_reenter.wren | 10 -- test/core/fiber/transfer.wren | 24 +++ test/core/fiber/transfer_direct_reenter.wren | 9 ++ .../core/fiber/transfer_indirect_reenter.wren | 18 +++ .../fiber/transfer_return_call_value.wren | 23 +++ .../fiber/transfer_return_transfer_value.wren | 13 ++ test/core/fiber/transfer_to_done.wren | 6 + test/core/fiber/transfer_to_error.wren | 7 + test/core/fiber/transfer_to_yielded.wren | 9 ++ test/core/fiber/transfer_with_value.wren | 24 +++ .../transfer_with_value_direct_reenter.wren | 8 + ...transfer_with_value_indirect_reenter.wren} | 13 +- .../fiber/transfer_with_value_to_done.wren | 6 + .../fiber/transfer_with_value_to_error.wren | 7 + .../fiber/transfer_with_value_to_yielded.wren | 9 ++ test/core/fiber/yield.wren | 6 +- test/core/fiber/yield_return_call_value.wren | 6 +- test/core/fiber/yield_return_run_value.wren | 12 -- .../fiber/yield_return_transfer_value.wren | 20 +++ test/core/fiber/yield_with_no_caller.wren | 11 +- test/core/fiber/yield_with_value.wren | 12 +- .../yield_with_value_with_no_caller.wren | 11 +- test/timer/sleep_called_fibers.wren | 21 +++ test/timer/sleep_fibers.wren | 8 +- test/timer/sleep_float.wren | 8 +- test/timer/sleep_return.wren | 3 + test/timer/sleep_zero.wren | 32 ++-- 49 files changed, 469 insertions(+), 350 deletions(-) rename test/core/fiber/{call_when_done.wren => call_done.wren} (100%) create mode 100644 test/core/fiber/call_error.wren create mode 100644 test/core/fiber/call_transferred.wren rename test/core/fiber/{call_with_value_when_done.wren => call_with_value_done.wren} (100%) create mode 100644 test/core/fiber/call_with_value_error.wren create mode 100644 test/core/fiber/call_with_value_transferred.wren delete mode 100644 test/core/fiber/run.wren delete mode 100644 test/core/fiber/run_direct_reenter.wren delete mode 100644 test/core/fiber/run_indirect_reenter.wren delete mode 100644 test/core/fiber/run_when_done.wren delete mode 100644 test/core/fiber/run_with_value.wren delete mode 100644 test/core/fiber/run_with_value_direct_reenter.wren create mode 100644 test/core/fiber/transfer.wren create mode 100644 test/core/fiber/transfer_direct_reenter.wren create mode 100644 test/core/fiber/transfer_indirect_reenter.wren create mode 100644 test/core/fiber/transfer_return_call_value.wren create mode 100644 test/core/fiber/transfer_return_transfer_value.wren create mode 100644 test/core/fiber/transfer_to_done.wren create mode 100644 test/core/fiber/transfer_to_error.wren create mode 100644 test/core/fiber/transfer_to_yielded.wren create mode 100644 test/core/fiber/transfer_with_value.wren create mode 100644 test/core/fiber/transfer_with_value_direct_reenter.wren rename test/core/fiber/{run_with_value_indirect_reenter.wren => transfer_with_value_indirect_reenter.wren} (57%) create mode 100644 test/core/fiber/transfer_with_value_to_done.wren create mode 100644 test/core/fiber/transfer_with_value_to_error.wren create mode 100644 test/core/fiber/transfer_with_value_to_yielded.wren delete mode 100644 test/core/fiber/yield_return_run_value.wren create mode 100644 test/core/fiber/yield_return_transfer_value.wren create mode 100644 test/timer/sleep_called_fibers.wren create mode 100644 test/timer/sleep_return.wren diff --git a/doc/site/core/fiber.markdown b/doc/site/core/fiber.markdown index 9e66e960..0791127e 100644 --- a/doc/site/core/fiber.markdown +++ b/doc/site/core/fiber.markdown @@ -19,6 +19,14 @@ fiber is run. Does not immediately start running the fiber. The currently executing fiber. +### Fiber.**suspend**() + +Pauses the current fiber, and stops the interpreter. Control returns to the +host application. + +To resume execution, the host application will need to invoke the interpreter +again. If there is still a reference to the suspended fiber, it can be resumed. + ### Fiber.**yield**() Pauses the current fiber and transfers control to the parent fiber. "Parent" @@ -119,10 +127,10 @@ Invokes the fiber or resumes the fiber if it is in a paused state and sets Whether the fiber's main function has completed and the fiber can no longer be run. This returns `false` if the fiber is currently running or has yielded. -### **run**() +### **transfer**() **TODO** -### **run**(value) +### **transfer**(value) **TODO** diff --git a/doc/site/fibers.markdown b/doc/site/fibers.markdown index f87158e8..addd8c93 100644 --- a/doc/site/fibers.markdown +++ b/doc/site/fibers.markdown @@ -158,17 +158,12 @@ Fibers have one more trick up their sleeves. When you execute a fiber using lets you build up a chain of fiber calls that will eventually unwind back to the main fiber when all of the called ones yield or finish. -This works fine for most uses, but sometimes you want something a little more -freeform. For example, you may be creating a [state -machine](http://en.wikipedia.org/wiki/Finite-state_machine) where each state is -a fiber. When you switch from one state to the next, you *don't* want to build -an implicit stack of fibers to return to. There is no "returning" in this case. -You just want to *transfer* to the next fiber and forget about the previous one -entirely. (This is analogous to [tail call -elimination](http://en.wikipedia.org/wiki/Tail_call) for regular function -calls.) +This is almost always what you want. But if you're doing something really low +level, like writing your own scheduler to manage a pool of fibers, you may not +want to treat them explicitly like a stack. -To enable this, fibers also have a `run()` method. This begins executing that -fiber, and "forgets" the previous one. If the running fiber yields or ends, it -will transfer control back to the last *called* one. (If there are no called -fibers, it will end execution.) +For rare cases like that, fibers also have a `transfer()` method. This switches +execution immediately to the transferred fiber. The previous one is suspended, +leaving it in whatever state it was in. You can resume the previous fiber by +transferring back to it, or even calling it. If you don't, execution stops when +the last transferred fiber returns. diff --git a/project/xcode/wren.xcodeproj/project.pbxproj b/project/xcode/wren.xcodeproj/project.pbxproj index e075920c..63a6e6b6 100644 --- a/project/xcode/wren.xcodeproj/project.pbxproj +++ b/project/xcode/wren.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 2901D7641B74F4050083A2C8 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2901D7621B74F4050083A2C8 /* timer.c */; }; - 2901D7651B74F4050083A2C8 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2901D7621B74F4050083A2C8 /* timer.c */; }; 29205C8F1AB4E5C90073018D /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C8E1AB4E5C90073018D /* main.c */; }; 29205C991AB4E6430073018D /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C921AB4E6430073018D /* wren_compiler.c */; }; 29205C9A1AB4E6430073018D /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C931AB4E6430073018D /* wren_core.c */; }; @@ -17,26 +16,11 @@ 29205C9D1AB4E6430073018D /* wren_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C961AB4E6430073018D /* wren_utils.c */; }; 29205C9E1AB4E6430073018D /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; }; 29205C9F1AB4E6430073018D /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; }; + 29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; + 29512C821B91F901008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; }; - 29C0888F1B6E7F0100B00CFD /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C0888E1B6E7F0100B00CFD /* libuv.a */; }; 29C8A92F1AB71C1C00DEC81D /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A92E1AB71C1C00DEC81D /* io.c */; }; 29C8A9331AB71FFF00DEC81D /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A9311AB71FFF00DEC81D /* vm.c */; }; - 29CE20181B7257720057FBA3 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE20171B7257720057FBA3 /* main.c */; }; - 29CE201B1B72577C0057FBA3 /* return_bool.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE20191B72577C0057FBA3 /* return_bool.c */; }; - 29CE201E1B7257840057FBA3 /* return_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE201C1B7257840057FBA3 /* return_double.c */; }; - 29CE20211B72578C0057FBA3 /* return_null.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CE201F1B72578C0057FBA3 /* return_null.c */; }; - 29CE20221B72586B0057FBA3 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A92E1AB71C1C00DEC81D /* io.c */; }; - 29CE20231B72586B0057FBA3 /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A9311AB71FFF00DEC81D /* vm.c */; }; - 29CE20241B72586B0057FBA3 /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C921AB4E6430073018D /* wren_compiler.c */; }; - 29CE20251B72586B0057FBA3 /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C931AB4E6430073018D /* wren_core.c */; }; - 29CE20261B72586B0057FBA3 /* wren_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C941AB4E6430073018D /* wren_debug.c */; }; - 29CE20271B72586B0057FBA3 /* wren_io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C951AB4E6430073018D /* wren_io.c */; }; - 29CE20281B72586B0057FBA3 /* wren_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29DE39511AC3A50A00987D41 /* wren_meta.c */; }; - 29CE20291B72586B0057FBA3 /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; }; - 29CE202A1B72586B0057FBA3 /* wren_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C961AB4E6430073018D /* wren_utils.c */; }; - 29CE202B1B72586B0057FBA3 /* wren_value.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C971AB4E6430073018D /* wren_value.c */; }; - 29CE202C1B72586B0057FBA3 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C981AB4E6430073018D /* wren_vm.c */; }; - 29CE202D1B7258E20057FBA3 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C0888E1B6E7F0100B00CFD /* libuv.a */; }; 29DE39531AC3A50A00987D41 /* wren_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29DE39511AC3A50A00987D41 /* wren_meta.c */; }; /* End PBXBuildFile section */ @@ -81,16 +65,16 @@ 29205CA61AB4E65E0073018D /* wren_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_utils.h; path = ../../src/vm/wren_utils.h; sourceTree = ""; }; 29205CA71AB4E65E0073018D /* wren_value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_value.h; path = ../../src/vm/wren_value.h; sourceTree = ""; }; 29205CA81AB4E65E0073018D /* wren_vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_vm.h; path = ../../src/vm/wren_vm.h; sourceTree = ""; }; + 29512C7F1B91F86E008C10E6 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 29512C801B91F8EB008C10E6 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv.a; sourceTree = ""; }; 296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = ""; }; 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_primitive.c; path = ../../src/vm/wren_primitive.c; sourceTree = ""; }; 2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = ""; }; 29AB1F061816E3AD004B501E /* wren */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = wren; sourceTree = BUILT_PRODUCTS_DIR; }; - 29C0888E1B6E7F0100B00CFD /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv/build/Release/libuv.a; sourceTree = ""; }; 29C8A92E1AB71C1C00DEC81D /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = ../../src/cli/io.c; sourceTree = ""; }; 29C8A9301AB71C3300DEC81D /* io.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/cli/io.h; sourceTree = ""; }; 29C8A9311AB71FFF00DEC81D /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = ../../src/cli/vm.c; sourceTree = ""; }; 29C8A9321AB71FFF00DEC81D /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vm.h; path = ../../src/cli/vm.h; sourceTree = ""; }; - 29D0099F1B7E397D000CE58C /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; 29D009A61B7E3993000CE58C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = ""; }; 29D009A81B7E39A8000CE58C /* foreign_class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = foreign_class.c; path = ../../test/api/foreign_class.c; sourceTree = ""; }; 29D009A91B7E39A8000CE58C /* foreign_class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = foreign_class.h; path = ../../test/api/foreign_class.h; sourceTree = ""; }; @@ -107,15 +91,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 29C0888F1B6E7F0100B00CFD /* libuv.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 29CE20071B7256660057FBA3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 29CE202D1B7258E20057FBA3 /* libuv.a in Frameworks */, + 29512C821B91F901008C10E6 /* libuv.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -123,6 +99,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -188,12 +165,12 @@ 29AB1EFD1816E3AD004B501E = { isa = PBXGroup; children = ( - 29C0888E1B6E7F0100B00CFD /* libuv.a */, 29205CA91AB4E67B0073018D /* cli */, 29205CAA1AB4E6840073018D /* include */, 2901D7611B74F3E20083A2C8 /* module */, 29205CA01AB4E6470073018D /* vm */, 29D0099A1B7E394F000CE58C /* api_test */, + 29512C801B91F8EB008C10E6 /* libuv.a */, 29AB1F071816E3AD004B501E /* Products */, ); sourceTree = ""; @@ -202,7 +179,7 @@ isa = PBXGroup; children = ( 29AB1F061816E3AD004B501E /* wren */, - 29CE200A1B7256660057FBA3 /* api_test */, + 29512C7F1B91F86E008C10E6 /* api_test */, ); name = Products; sourceTree = ""; @@ -255,7 +232,7 @@ ); name = api_test; productName = api_test; - productReference = 29D0099F1B7E397D000CE58C /* api_test */; + productReference = 29512C7F1B91F86E008C10E6 /* api_test */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ @@ -315,21 +292,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 29D009B11B7E39FE000CE58C /* io.c in Sources */, - 29D009B21B7E39FE000CE58C /* vm.c in Sources */, - 29D009B31B7E39FE000CE58C /* wren_compiler.c in Sources */, - 29D009B41B7E39FE000CE58C /* wren_core.c in Sources */, - 29D009B51B7E39FE000CE58C /* wren_debug.c in Sources */, - 29D009B61B7E39FE000CE58C /* wren_io.c in Sources */, - 29D009B71B7E39FE000CE58C /* wren_meta.c in Sources */, - 29D009B81B7E39FE000CE58C /* wren_primitive.c in Sources */, - 29D009B91B7E39FE000CE58C /* wren_utils.c in Sources */, - 29D009BA1B7E39FE000CE58C /* wren_value.c in Sources */, - 29D009BB1B7E39FE000CE58C /* wren_vm.c in Sources */, - 29D009A71B7E3993000CE58C /* main.c in Sources */, - 29D009B01B7E39A8000CE58C /* value.c in Sources */, - 29D009AF1B7E39A8000CE58C /* returns.c in Sources */, - 29D009AE1B7E39A8000CE58C /* foreign_class.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -415,9 +377,9 @@ CLANG_WARN_UNREACHABLE_CODE = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_PEDANTIC = NO; - LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; + LIBRARY_SEARCH_PATHS = ../../build; PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; + USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include; }; name = Debug; }; @@ -428,51 +390,9 @@ CLANG_WARN_UNREACHABLE_CODE = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_PEDANTIC = NO; - LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; + LIBRARY_SEARCH_PATHS = ../../build; PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; - }; - name = Release; - }; - 29CE200E1B7256660057FBA3 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_PEDANTIC = NO; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; - }; - name = Debug; - }; - 29CE200F1B7256660057FBA3 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_PEDANTIC = NO; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - LIBRARY_SEARCH_PATHS = ../../build/libuv/build/Release; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = ../../build/libuv/include; + USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include; }; name = Release; }; @@ -492,9 +412,11 @@ ); GCC_WARN_PEDANTIC = NO; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LIBRARY_SEARCH_PATHS = ../../build; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include; }; name = Debug; }; @@ -510,9 +432,11 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_PEDANTIC = NO; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LIBRARY_SEARCH_PATHS = ../../build; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include; }; name = Release; }; @@ -544,6 +468,7 @@ 29D009A51B7E397D000CE58C /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/script/test.py b/script/test.py index dcd83756..e8542947 100755 --- a/script/test.py +++ b/script/test.py @@ -149,7 +149,7 @@ class Test: def validate_runtime_error(self, error_lines): if len(error_lines) < 2: - self.fail('Expected runtime error "{0} and got none.', + self.fail('Expected runtime error "{0}" and got none.', self.runtime_error_message) return diff --git a/src/module/timer.c b/src/module/timer.c index f164771b..466a269d 100644 --- a/src/module/timer.c +++ b/src/module/timer.c @@ -14,16 +14,36 @@ static const char* timerLibSource = " if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n" " if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n" " startTimer_(milliseconds, Fiber.current)\n" -" Fiber.yield()\n" +"\n" +" runNextScheduled_()\n" +" }\n" +"\n" +" // TODO: Once the CLI modules are more fleshed out, find a better place to\n" +" // put this.\n" +" static schedule(callable) {\n" +" if (__scheduled == null) __scheduled = []\n" +" __scheduled.add(Fiber.new {\n" +" callable.call()\n" +" runNextScheduled_()\n" +" })\n" " }\n" "\n" " foreign static startTimer_(milliseconds, fiber)\n" "\n" " // Called by native code.\n" " static resumeTimer_(fiber) {\n" -" fiber.run()\n" +" fiber.transfer()\n" " }\n" -"}\n"; +"\n" +" static runNextScheduled_() {\n" +" if (__scheduled == null || __scheduled.isEmpty) {\n" +" Fiber.suspend()\n" +" } else {\n" +" __scheduled.removeAt(0).transfer()\n" +" }\n" +" }\n" +"}\n" +"\n"; // The Wren method to call when a timer has completed. static WrenMethod* resumeTimer; diff --git a/src/module/timer.wren b/src/module/timer.wren index a6ea328d..7dc173f5 100644 --- a/src/module/timer.wren +++ b/src/module/timer.wren @@ -3,13 +3,33 @@ class Timer { if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.") if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.") startTimer_(milliseconds, Fiber.current) - Fiber.yield() + + runNextScheduled_() + } + + // TODO: Once the CLI modules are more fleshed out, find a better place to + // put this. + static schedule(callable) { + if (__scheduled == null) __scheduled = [] + __scheduled.add(Fiber.new { + callable.call() + runNextScheduled_() + }) } foreign static startTimer_(milliseconds, fiber) // Called by native code. static resumeTimer_(fiber) { - fiber.run() + fiber.transfer() + } + + static runNextScheduled_() { + if (__scheduled == null || __scheduled.isEmpty) { + Fiber.suspend() + } else { + __scheduled.removeAt(0).transfer() + } } } + diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index d0e1d690..0a0a8499 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -286,49 +286,67 @@ DEF_PRIMITIVE(fiber_abort) return PRIM_ERROR; } +// Transfer execution to [fiber] coming from the current fiber whose stack has +// [args]. +// +// [isCall] is true if [fiber] is being called and not transferred. +// +// [hasValue] is true if a value in [args] is being passed to the new fiber. +// Otherwise, `null` is implicitly being passed. +static PrimitiveResult runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, + bool isCall, bool hasValue) +{ + if (isCall) + { + if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); + + // Remember who ran it. + fiber->caller = vm->fiber; + } + else + { + // Edge case: If we are transferring to ourself, immediately return the + // value. We can't treat this like an actual transfer since when we set the + // return below, it would overwrite the fiber being transferred to. + if (fiber == vm->fiber) RETURN_VAL(hasValue ? args[1] : NULL_VAL); + } + + if (fiber->numFrames == 0) + { + args[0] = wrenStringFormat(vm, "Cannot $ a finished fiber.", + isCall ? "call" : "transfer to"); + return PRIM_ERROR; + } + + if (fiber->error != NULL) + { + args[0] = wrenStringFormat(vm, "Cannot $ an aborted fiber.", + isCall ? "call" : "transfer to"); + return PRIM_ERROR; + } + + // When the calling fiber resumes, we'll store the result of the call in its + // stack. If the call has two arguments (the fiber and the value), we only + // need one slot for the result, so discard the other slot now. + if (hasValue) vm->fiber->stackTop--; + + // If the fiber was paused, make yield() or transfer() return the result. + if (fiber->stackTop > fiber->stack) + { + *(fiber->stackTop - 1) = hasValue ? args[1] : NULL_VAL; + } + + return PRIM_RUN_FIBER; +} + DEF_PRIMITIVE(fiber_call) { - ObjFiber* runFiber = AS_FIBER(args[0]); - - if (runFiber->numFrames == 0) RETURN_ERROR("Cannot call a finished fiber."); - if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); - - // Remember who ran it. - runFiber->caller = fiber; - - // If the fiber was yielded, make the yield call return null. - if (runFiber->stackTop > runFiber->stack) - { - *(runFiber->stackTop - 1) = NULL_VAL; - } - - return PRIM_RUN_FIBER; + return runFiber(vm, AS_FIBER(args[0]), args, true, false); } DEF_PRIMITIVE(fiber_call1) { - ObjFiber* runFiber = AS_FIBER(args[0]); - - if (runFiber->numFrames == 0) RETURN_ERROR("Cannot call a finished fiber."); - if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); - - // Remember who ran it. - runFiber->caller = fiber; - - // If the fiber was yielded, make the yield call return the value passed to - // run. - if (runFiber->stackTop > runFiber->stack) - { - *(runFiber->stackTop - 1) = args[1]; - } - - // When the calling fiber resumes, we'll store the result of the run call - // in its stack. Since fiber.run(value) has two arguments (the fiber and the - // value) and we only need one slot for the result, discard the other slot - // now. - fiber->stackTop--; - - return PRIM_RUN_FIBER; + return runFiber(vm, AS_FIBER(args[0]), args, true, true); } DEF_PRIMITIVE(fiber_current) @@ -349,55 +367,28 @@ DEF_PRIMITIVE(fiber_isDone) RETURN_BOOL(runFiber->numFrames == 0 || runFiber->error != NULL); } -DEF_PRIMITIVE(fiber_run) +DEF_PRIMITIVE(fiber_suspend) { - ObjFiber* runFiber = AS_FIBER(args[0]); - - if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber."); - - // If the fiber was yielded, make the yield call return null. - if (runFiber->caller == NULL && runFiber->stackTop > runFiber->stack) - { - *(runFiber->stackTop - 1) = NULL_VAL; - } - - // Unlike run, this does not remember the calling fiber. Instead, it - // remember's *that* fiber's caller. You can think of it like tail call - // elimination. The switched-from fiber is discarded and when the switched - // to fiber completes or yields, control passes to the switched-from fiber's - // caller. - runFiber->caller = fiber->caller; - + // Switching to a null fiber tells the interpreter to stop and exit. + args[0] = NULL_VAL; return PRIM_RUN_FIBER; } -DEF_PRIMITIVE(fiber_run1) +DEF_PRIMITIVE(fiber_transfer) { - ObjFiber* runFiber = AS_FIBER(args[0]); + return runFiber(vm, AS_FIBER(args[0]), args, false, false); +} - if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber."); - - // If the fiber was yielded, make the yield call return the value passed to - // run. - if (runFiber->caller == NULL && runFiber->stackTop > runFiber->stack) - { - *(runFiber->stackTop - 1) = args[1]; - } - - // Unlike run, this does not remember the calling fiber. Instead, it - // remember's *that* fiber's caller. You can think of it like tail call - // elimination. The switched-from fiber is discarded and when the switched - // to fiber completes or yields, control passes to the switched-from fiber's - // caller. - runFiber->caller = fiber->caller; - - return PRIM_RUN_FIBER; +DEF_PRIMITIVE(fiber_transfer1) +{ + return runFiber(vm, AS_FIBER(args[0]), args, false, true); } DEF_PRIMITIVE(fiber_try) { ObjFiber* runFiber = AS_FIBER(args[0]); + // TODO: Use runFiber(). if (runFiber->numFrames == 0) RETURN_ERROR("Cannot try a finished fiber."); if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called."); @@ -420,7 +411,7 @@ DEF_PRIMITIVE(fiber_yield) ObjFiber* caller = fiber->caller; fiber->caller = NULL; fiber->callerIsTrying = false; - + // If we don't have any other pending fibers, jump all the way out of the // interpreter. if (caller == NULL) @@ -1350,14 +1341,15 @@ void wrenInitializeCore(WrenVM* vm) PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new); PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort); PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current); + PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend); PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield); PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1); PRIMITIVE(vm->fiberClass, "call()", fiber_call); PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1); PRIMITIVE(vm->fiberClass, "error", fiber_error); PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone); - PRIMITIVE(vm->fiberClass, "run()", fiber_run); - PRIMITIVE(vm->fiberClass, "run(_)", fiber_run1); + PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer); + PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1); PRIMITIVE(vm->fiberClass, "try()", fiber_try); vm->fnClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Fn")); diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 89107058..cb2177b4 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -396,20 +396,24 @@ static void callForeign(WrenVM* vm, ObjFiber* fiber, // // Returns the fiber that should receive the error or `NULL` if no fiber // caught it. -static ObjFiber* runtimeError(ObjFiber* fiber, Value error) +static ObjFiber* runtimeError(WrenVM* vm, ObjFiber* fiber, Value error) { ASSERT(fiber->error == NULL, "Can only fail once."); // Store the error in the fiber so it can be accessed later. fiber->error = AS_STRING(error); + // Unhook the caller since we will never resume and return to it. + ObjFiber* caller = fiber->caller; + fiber->caller = NULL; + // If the caller ran this fiber using "try", give it the error. if (fiber->callerIsTrying) { - ObjFiber* caller = fiber->caller; - // Make the caller's try method return the error message. *(caller->stackTop - 1) = OBJ_VAL(fiber->error); + + vm->fiber = caller; return caller; } @@ -735,7 +739,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) do \ { \ STORE_FRAME(); \ - fiber = runtimeError(fiber, error); \ + fiber = runtimeError(vm, fiber, error); \ if (fiber == NULL) return WREN_RESULT_RUNTIME_ERROR; \ LOAD_FRAME(); \ DISPATCH(); \ @@ -1108,7 +1112,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) if (fiber->caller == NULL) return WREN_RESULT_SUCCESS; // We have a calling fiber to resume. - fiber = fiber->caller; + ObjFiber* callingFiber = fiber->caller; + fiber->caller = NULL; + + fiber = callingFiber; vm->fiber = fiber; // Store the result in the resuming fiber. diff --git a/test/core/fiber/call_when_done.wren b/test/core/fiber/call_done.wren similarity index 100% rename from test/core/fiber/call_when_done.wren rename to test/core/fiber/call_done.wren diff --git a/test/core/fiber/call_error.wren b/test/core/fiber/call_error.wren new file mode 100644 index 00000000..57742ec9 --- /dev/null +++ b/test/core/fiber/call_error.wren @@ -0,0 +1,7 @@ +var fiber = Fiber.new { + Fiber.abort("Error!") + IO.print("should not get here") +} + +fiber.try() +fiber.call() // expect runtime error: Cannot call an aborted fiber. diff --git a/test/core/fiber/call_return_implicit_null.wren b/test/core/fiber/call_return_implicit_null.wren index cde91b15..0ce9a9a0 100644 --- a/test/core/fiber/call_return_implicit_null.wren +++ b/test/core/fiber/call_return_implicit_null.wren @@ -1,6 +1,5 @@ var fiber = Fiber.new { - IO.print("fiber") + IO.print("fiber") // expect: fiber } -var result = fiber.call() // expect: fiber -IO.print(result) // expect: null +IO.print(fiber.call()) // expect: null diff --git a/test/core/fiber/call_return_value.wren b/test/core/fiber/call_return_value.wren index 6203f845..c4dd58d9 100644 --- a/test/core/fiber/call_return_value.wren +++ b/test/core/fiber/call_return_value.wren @@ -1,7 +1,6 @@ var fiber = Fiber.new { IO.print("fiber") - return "result" + return "result" // expect: fiber } -var result = fiber.call() // expect: fiber -IO.print(result) // expect: result +IO.print(fiber.call()) // expect: result diff --git a/test/core/fiber/call_transferred.wren b/test/core/fiber/call_transferred.wren new file mode 100644 index 00000000..b174ae54 --- /dev/null +++ b/test/core/fiber/call_transferred.wren @@ -0,0 +1,12 @@ +var main = Fiber.current + +var fiber = Fiber.new { + IO.print("transferred") + IO.print(main.transfer()) + IO.print("called") +} + +fiber.transfer() // expect: transferred +IO.print("main") // expect: main +fiber.call() // expect: null + // expect: called diff --git a/test/core/fiber/call_with_value_when_done.wren b/test/core/fiber/call_with_value_done.wren similarity index 100% rename from test/core/fiber/call_with_value_when_done.wren rename to test/core/fiber/call_with_value_done.wren diff --git a/test/core/fiber/call_with_value_error.wren b/test/core/fiber/call_with_value_error.wren new file mode 100644 index 00000000..e5513755 --- /dev/null +++ b/test/core/fiber/call_with_value_error.wren @@ -0,0 +1,7 @@ +var fiber = Fiber.new { + Fiber.abort("Error!") + IO.print("should not get here") +} + +fiber.try() +fiber.call("value") // expect runtime error: Cannot call an aborted fiber. diff --git a/test/core/fiber/call_with_value_indirect_reenter.wren b/test/core/fiber/call_with_value_indirect_reenter.wren index 6547fce8..6d7030f4 100644 --- a/test/core/fiber/call_with_value_indirect_reenter.wren +++ b/test/core/fiber/call_with_value_indirect_reenter.wren @@ -1,12 +1,9 @@ -var a -var b - -a = Fiber.new { - b.call(3) // expect runtime error: Fiber has already been called. +var A = Fiber.new { + B.call(3) // expect runtime error: Fiber has already been called. } -b = Fiber.new { - a.call(2) +var B = Fiber.new { + A.call(2) } -b.call(1) +B.call(1) diff --git a/test/core/fiber/call_with_value_transferred.wren b/test/core/fiber/call_with_value_transferred.wren new file mode 100644 index 00000000..b0e1cc99 --- /dev/null +++ b/test/core/fiber/call_with_value_transferred.wren @@ -0,0 +1,12 @@ +var main = Fiber.current + +var fiber = Fiber.new { + IO.print("transferred") + IO.print(main.transfer()) + IO.print("called") +} + +fiber.transfer() // expect: transferred +IO.print("main") // expect: main +fiber.call("value") // expect: value + // expect: called diff --git a/test/core/fiber/run.wren b/test/core/fiber/run.wren deleted file mode 100644 index a1add056..00000000 --- a/test/core/fiber/run.wren +++ /dev/null @@ -1,9 +0,0 @@ -var fiber = Fiber.new { - IO.print("fiber") -} - -IO.print("before") // expect: before -fiber.run() // expect: fiber - -// This does not get run since we exit when the run fiber completes. -IO.print("nope") diff --git a/test/core/fiber/run_direct_reenter.wren b/test/core/fiber/run_direct_reenter.wren deleted file mode 100644 index 84fbe024..00000000 --- a/test/core/fiber/run_direct_reenter.wren +++ /dev/null @@ -1,10 +0,0 @@ -var fiber - -fiber = Fiber.new { - IO.print(1) // expect: 1 - fiber.run() - IO.print(2) // expect: 2 -} - -fiber.call() -IO.print(3) // expect: 3 \ No newline at end of file diff --git a/test/core/fiber/run_indirect_reenter.wren b/test/core/fiber/run_indirect_reenter.wren deleted file mode 100644 index 01eaf6db..00000000 --- a/test/core/fiber/run_indirect_reenter.wren +++ /dev/null @@ -1,20 +0,0 @@ -var a -var b - -a = Fiber.new { - IO.print(2) - b.run() - IO.print("nope") -} - -b = Fiber.new { - IO.print(1) - a.run() - IO.print(3) -} - -b.call() -// expect: 1 -// expect: 2 -// expect: 3 -IO.print(4) // expect: 4 diff --git a/test/core/fiber/run_when_done.wren b/test/core/fiber/run_when_done.wren deleted file mode 100644 index 1e9c1db0..00000000 --- a/test/core/fiber/run_when_done.wren +++ /dev/null @@ -1,13 +0,0 @@ -var a = Fiber.new { - IO.print("run") -} - -// Run a through an intermediate fiber since it will get discarded and we need -// to return to the main one after a completes. -var b = Fiber.new { - a.run() - IO.print("nope") -} - -b.call() // expect: run -a.run() // expect runtime error: Cannot run a finished fiber. diff --git a/test/core/fiber/run_with_value.wren b/test/core/fiber/run_with_value.wren deleted file mode 100644 index 638b9fd1..00000000 --- a/test/core/fiber/run_with_value.wren +++ /dev/null @@ -1,11 +0,0 @@ -var fiber = Fiber.new { - IO.print("fiber") -} - -// The first value passed to the fiber is ignored, since there's no yield call -// to return it. -IO.print("before") // expect: before -fiber.run("ignored") // expect: fiber - -// This does not get run since we exit when the run fiber completes. -IO.print("nope") diff --git a/test/core/fiber/run_with_value_direct_reenter.wren b/test/core/fiber/run_with_value_direct_reenter.wren deleted file mode 100644 index 03ee62e1..00000000 --- a/test/core/fiber/run_with_value_direct_reenter.wren +++ /dev/null @@ -1,10 +0,0 @@ -var fiber - -fiber = Fiber.new { - IO.print(1) // expect: 1 - fiber.run("ignored") - IO.print(2) // expect: 2 -} - -fiber.call() -IO.print(3) // expect: 3 \ No newline at end of file diff --git a/test/core/fiber/transfer.wren b/test/core/fiber/transfer.wren new file mode 100644 index 00000000..b97fe109 --- /dev/null +++ b/test/core/fiber/transfer.wren @@ -0,0 +1,24 @@ +var a = Fiber.new { + IO.print("a") +} + +var b = Fiber.new { + IO.print("b before") + a.transfer() + IO.print("b after") +} + +var c = Fiber.new { + IO.print("c before") + b.transfer() + IO.print("c after") +} + +IO.print("start") // expect: start + +c.transfer() +// expect: c before +// expect: b before +// expect: a + +// Nothing else gets run since the interpreter stops after a completes. diff --git a/test/core/fiber/transfer_direct_reenter.wren b/test/core/fiber/transfer_direct_reenter.wren new file mode 100644 index 00000000..ce424e0d --- /dev/null +++ b/test/core/fiber/transfer_direct_reenter.wren @@ -0,0 +1,9 @@ +var F = Fiber.new { + IO.print(1) // expect: 1 + IO.print(F.transfer()) // expect: null + IO.print(2) // expect: 2 +} + +F.call() +// F remembers its original caller so transfers back to main. +IO.print(3) // expect: 3 diff --git a/test/core/fiber/transfer_indirect_reenter.wren b/test/core/fiber/transfer_indirect_reenter.wren new file mode 100644 index 00000000..2a2cfaff --- /dev/null +++ b/test/core/fiber/transfer_indirect_reenter.wren @@ -0,0 +1,18 @@ +var A = Fiber.new { + IO.print(2) + B.transfer() + IO.print("nope") +} + +var B = Fiber.new { + IO.print(1) + A.transfer() + IO.print(3) +} + +B.call() +// expect: 1 +// expect: 2 +// expect: 3 +// B remembers its original caller so returns to main. +IO.print(4) // expect: 4 diff --git a/test/core/fiber/transfer_return_call_value.wren b/test/core/fiber/transfer_return_call_value.wren new file mode 100644 index 00000000..b12b9261 --- /dev/null +++ b/test/core/fiber/transfer_return_call_value.wren @@ -0,0 +1,23 @@ +var main = Fiber.current + +var fiber = Fiber.new { + IO.print("fiber 1") + IO.print(main.transfer()) + + // Yield to bounce back to main and clear the caller so we don't get a + // double call() error below. + Fiber.yield() + + IO.print(main.transfer()) +} + +fiber.transfer() // expect: fiber 1 +IO.print("main 1") // expect: main 1 +fiber.call("call 1") // expect: call 1 + +IO.print("main 2") // expect: main 2 +// Transfer back into the fiber so it has a NULL caller. +fiber.transfer() + +fiber.call() // expect: null +IO.print("main 3") // expect: main 3 diff --git a/test/core/fiber/transfer_return_transfer_value.wren b/test/core/fiber/transfer_return_transfer_value.wren new file mode 100644 index 00000000..9bde5a0f --- /dev/null +++ b/test/core/fiber/transfer_return_transfer_value.wren @@ -0,0 +1,13 @@ +var main = Fiber.current + +var fiber = Fiber.new { + IO.print("fiber") + IO.print(main.transfer()) +} + +fiber.transfer() // expect: fiber +IO.print("main") // expect: main +fiber.transfer("transfer") // expect: transfer + +// This does not get run since we exit when the transferred fiber completes. +IO.print("nope") diff --git a/test/core/fiber/transfer_to_done.wren b/test/core/fiber/transfer_to_done.wren new file mode 100644 index 00000000..8e330850 --- /dev/null +++ b/test/core/fiber/transfer_to_done.wren @@ -0,0 +1,6 @@ +var a = Fiber.new { + IO.print("run") +} + +a.call() // expect: run +a.transfer() // expect runtime error: Cannot transfer to a finished fiber. diff --git a/test/core/fiber/transfer_to_error.wren b/test/core/fiber/transfer_to_error.wren new file mode 100644 index 00000000..c50e770b --- /dev/null +++ b/test/core/fiber/transfer_to_error.wren @@ -0,0 +1,7 @@ +var a = Fiber.new { + Fiber.abort("Error!") + IO.print("should not get here") +} + +a.try() +a.transfer() // expect runtime error: Cannot transfer to an aborted fiber. diff --git a/test/core/fiber/transfer_to_yielded.wren b/test/core/fiber/transfer_to_yielded.wren new file mode 100644 index 00000000..f74f1526 --- /dev/null +++ b/test/core/fiber/transfer_to_yielded.wren @@ -0,0 +1,9 @@ +var fiber = Fiber.new { + IO.print("called") + IO.print(Fiber.yield()) + IO.print("transferred") +} + +fiber.call() // expect: called +fiber.transfer() // expect: null + // expect: transferred diff --git a/test/core/fiber/transfer_with_value.wren b/test/core/fiber/transfer_with_value.wren new file mode 100644 index 00000000..efe8f956 --- /dev/null +++ b/test/core/fiber/transfer_with_value.wren @@ -0,0 +1,24 @@ +var a = Fiber.new { + IO.print("a") +} + +var b = Fiber.new { + IO.print("b before") + a.transfer("ignored") + IO.print("b after") +} + +var c = Fiber.new { + IO.print("c before") + b.transfer("ignored") + IO.print("c after") +} + +IO.print("start") // expect: start + +c.transfer("ignored") +// expect: c before +// expect: b before +// expect: a + +// Nothing else gets run since the interpreter stops after a completes. diff --git a/test/core/fiber/transfer_with_value_direct_reenter.wren b/test/core/fiber/transfer_with_value_direct_reenter.wren new file mode 100644 index 00000000..b5b92b1f --- /dev/null +++ b/test/core/fiber/transfer_with_value_direct_reenter.wren @@ -0,0 +1,8 @@ +var F = Fiber.new { + IO.print(1) // expect: 1 + IO.print(F.transfer("value")) // expect: value + IO.print(2) // expect: 2 +} + +F.call() +IO.print(3) // expect: 3 diff --git a/test/core/fiber/run_with_value_indirect_reenter.wren b/test/core/fiber/transfer_with_value_indirect_reenter.wren similarity index 57% rename from test/core/fiber/run_with_value_indirect_reenter.wren rename to test/core/fiber/transfer_with_value_indirect_reenter.wren index d5aa24fb..d98092c6 100644 --- a/test/core/fiber/run_with_value_indirect_reenter.wren +++ b/test/core/fiber/transfer_with_value_indirect_reenter.wren @@ -1,19 +1,16 @@ -var a -var b - -a = Fiber.new { +var A = Fiber.new { IO.print(2) - b.run("ignored") + B.transfer("ignored") IO.print("nope") } -b = Fiber.new { +var B = Fiber.new { IO.print(1) - a.run("ignored") + A.transfer("ignored") IO.print(3) } -b.call() +B.call() // expect: 1 // expect: 2 // expect: 3 diff --git a/test/core/fiber/transfer_with_value_to_done.wren b/test/core/fiber/transfer_with_value_to_done.wren new file mode 100644 index 00000000..980b6e11 --- /dev/null +++ b/test/core/fiber/transfer_with_value_to_done.wren @@ -0,0 +1,6 @@ +var a = Fiber.new { + IO.print("run") +} + +a.call() // expect: run +a.transfer("blah") // expect runtime error: Cannot transfer to a finished fiber. diff --git a/test/core/fiber/transfer_with_value_to_error.wren b/test/core/fiber/transfer_with_value_to_error.wren new file mode 100644 index 00000000..f8ec2f97 --- /dev/null +++ b/test/core/fiber/transfer_with_value_to_error.wren @@ -0,0 +1,7 @@ +var a = Fiber.new { + Fiber.abort("Error!") + IO.print("should not get here") +} + +a.try() +a.transfer("blah") // expect runtime error: Cannot transfer to an aborted fiber. diff --git a/test/core/fiber/transfer_with_value_to_yielded.wren b/test/core/fiber/transfer_with_value_to_yielded.wren new file mode 100644 index 00000000..016dc790 --- /dev/null +++ b/test/core/fiber/transfer_with_value_to_yielded.wren @@ -0,0 +1,9 @@ +var fiber = Fiber.new { + IO.print("called") + IO.print(Fiber.yield()) + IO.print("transferred") +} + +fiber.call() // expect: called +fiber.transfer("value") // expect: value + // expect: transferred diff --git a/test/core/fiber/yield.wren b/test/core/fiber/yield.wren index 412bb6f5..ea4491c1 100644 --- a/test/core/fiber/yield.wren +++ b/test/core/fiber/yield.wren @@ -6,9 +6,9 @@ var fiber = Fiber.new { IO.print("fiber 3") } -var result = fiber.call() // expect: fiber 1 +fiber.call() // expect: fiber 1 IO.print("main 1") // expect: main 1 -result = fiber.call() // expect: fiber 2 +fiber.call() // expect: fiber 2 IO.print("main 2") // expect: main 2 -result = fiber.call() // expect: fiber 3 +fiber.call() // expect: fiber 3 IO.print("main 3") // expect: main 3 diff --git a/test/core/fiber/yield_return_call_value.wren b/test/core/fiber/yield_return_call_value.wren index c728a654..b858fd26 100644 --- a/test/core/fiber/yield_return_call_value.wren +++ b/test/core/fiber/yield_return_call_value.wren @@ -1,9 +1,7 @@ var fiber = Fiber.new { IO.print("fiber 1") - var result = Fiber.yield() - IO.print(result) - result = Fiber.yield() - IO.print(result) + IO.print(Fiber.yield()) + IO.print(Fiber.yield()) } fiber.call() // expect: fiber 1 diff --git a/test/core/fiber/yield_return_run_value.wren b/test/core/fiber/yield_return_run_value.wren deleted file mode 100644 index b43317e3..00000000 --- a/test/core/fiber/yield_return_run_value.wren +++ /dev/null @@ -1,12 +0,0 @@ -var fiber = Fiber.new { - IO.print("fiber") - var result = Fiber.yield() - IO.print(result) -} - -fiber.call() // expect: fiber -IO.print("main") // expect: main -fiber.run("run") // expect: run - -// This does not get run since we exit when the run fiber completes. -IO.print("nope") diff --git a/test/core/fiber/yield_return_transfer_value.wren b/test/core/fiber/yield_return_transfer_value.wren new file mode 100644 index 00000000..a345ea7c --- /dev/null +++ b/test/core/fiber/yield_return_transfer_value.wren @@ -0,0 +1,20 @@ +var main = Fiber.current + +var a = Fiber.new { + IO.print("a") + IO.print(Fiber.yield()) +} + +var b = Fiber.new { + IO.print("b") + IO.print(Fiber.yield()) + + a.call() + a.transfer("value") +} + +b.call() // expect: b +b.transfer() +// expect: null +// expect: a +// expect: value diff --git a/test/core/fiber/yield_with_no_caller.wren b/test/core/fiber/yield_with_no_caller.wren index 7f6b606d..49b975c2 100644 --- a/test/core/fiber/yield_with_no_caller.wren +++ b/test/core/fiber/yield_with_no_caller.wren @@ -1,11 +1,12 @@ var a = Fiber.new { IO.print("before") // expect: before Fiber.yield() + IO.print("not reached") } -// Run a chain of fibers. Since none of them are called, they all get discarded -// and there is no remaining caller. -var b = Fiber.new { a.run() } -var c = Fiber.new { b.run() } -c.run() +// Transfer through a chain of fibers. Since none of them are called, they all +// get discarded and there is no remaining caller. +var b = Fiber.new { a.transfer() } +var c = Fiber.new { b.transfer() } +c.transfer() IO.print("not reached") \ No newline at end of file diff --git a/test/core/fiber/yield_with_value.wren b/test/core/fiber/yield_with_value.wren index 4d909de7..979c3c81 100644 --- a/test/core/fiber/yield_with_value.wren +++ b/test/core/fiber/yield_with_value.wren @@ -6,9 +6,9 @@ var fiber = Fiber.new { IO.print("fiber 3") } -var result = fiber.call() // expect: fiber 1 -IO.print(result) // expect: yield 1 -result = fiber.call() // expect: fiber 2 -IO.print(result) // expect: yield 2 -result = fiber.call() // expect: fiber 3 -IO.print(result) // expect: null +IO.print(fiber.call()) // expect: fiber 1 + // expect: yield 1 +IO.print(fiber.call()) // expect: fiber 2 + // expect: yield 2 +IO.print(fiber.call()) // expect: fiber 3 + // expect: null diff --git a/test/core/fiber/yield_with_value_with_no_caller.wren b/test/core/fiber/yield_with_value_with_no_caller.wren index 960c70aa..615e96cd 100644 --- a/test/core/fiber/yield_with_value_with_no_caller.wren +++ b/test/core/fiber/yield_with_value_with_no_caller.wren @@ -1,11 +1,12 @@ var a = Fiber.new { IO.print("before") // expect: before Fiber.yield(1) + IO.print("not reached") } -// Run a chain of fibers. Since none of them are called, they all get discarded -// and there is no remaining caller. -var b = Fiber.new { a.run() } -var c = Fiber.new { b.run() } -c.run() +// Transfer through a chain of fibers. Since none of them are called, they all +// get discarded and there is no remaining caller. +var b = Fiber.new { a.transfer() } +var c = Fiber.new { b.transfer() } +c.transfer() IO.print("not reached") diff --git a/test/timer/sleep_called_fibers.wren b/test/timer/sleep_called_fibers.wren new file mode 100644 index 00000000..a5598ef0 --- /dev/null +++ b/test/timer/sleep_called_fibers.wren @@ -0,0 +1,21 @@ +import "timer" for Timer + +var a = Fiber.new { + IO.print("a before") + Timer.sleep(10) + IO.print("a after") +} + +var b = Fiber.new { + IO.print("b before") + a.call() + IO.print("b after") +} + +// All fibers are suspended since they were directly called and not scheduled. +IO.print("before") // expect: before +b.call() // expect: b before + // expect: a before + // expect: a after + // expect: b after +IO.print("done") // expect: done diff --git a/test/timer/sleep_fibers.wren b/test/timer/sleep_fibers.wren index 88e308c9..cc74568e 100644 --- a/test/timer/sleep_fibers.wren +++ b/test/timer/sleep_fibers.wren @@ -1,14 +1,14 @@ import "timer" for Timer -Fiber.new { +Timer.schedule { Timer.sleep(2) IO.print("a") -}.call() +} -Fiber.new { +Timer.schedule { Timer.sleep(1) IO.print("b") -}.call() +} IO.print("main") Timer.sleep(3) diff --git a/test/timer/sleep_float.wren b/test/timer/sleep_float.wren index f04b6111..09993093 100644 --- a/test/timer/sleep_float.wren +++ b/test/timer/sleep_float.wren @@ -1,15 +1,15 @@ import "timer" for Timer // These are both rounded to 1, so "a" should complete first. -Fiber.new { +Timer.schedule { Timer.sleep(1.5) IO.print("a") -}.call() +} -Fiber.new { +Timer.schedule { Timer.sleep(1.3) IO.print("b") -}.call() +} IO.print("main") Timer.sleep(3) diff --git a/test/timer/sleep_return.wren b/test/timer/sleep_return.wren new file mode 100644 index 00000000..6b6484d2 --- /dev/null +++ b/test/timer/sleep_return.wren @@ -0,0 +1,3 @@ +import "timer" for Timer + +IO.print(Timer.sleep(0)) // expect: null diff --git a/test/timer/sleep_zero.wren b/test/timer/sleep_zero.wren index 569ffe13..3074fff3 100644 --- a/test/timer/sleep_zero.wren +++ b/test/timer/sleep_zero.wren @@ -1,27 +1,27 @@ import "timer" for Timer -Fiber.new { +Timer.schedule { + IO.print("a before") Timer.sleep(0) - IO.print("a") -}.call() + IO.print("a after") +} -Fiber.new { +Timer.schedule { + IO.print("b before") Timer.sleep(0) - IO.print("b") -}.call() - -Fiber.new { - Timer.sleep(0) - IO.print("c") -}.call() + IO.print("b after") +} IO.print("main") -Timer.sleep(0) // This is enough to let the other fiber run. +Timer.sleep(0) // This is enough to let the other fibers run to their sleep. +IO.print("main after") +Timer.sleep(0) // And now their sleeps complete. IO.print("done") // expect: main -// Run in the order they were enqueued. -// expect: a -// expect: b -// expect: c +// expect: a before +// expect: b before +// expect: main after +// expect: a after +// expect: b after // expect: done