aboutsummaryrefslogtreecommitdiff
path: root/src/lanes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lanes.cpp')
-rw-r--r--src/lanes.cpp277
1 files changed, 131 insertions, 146 deletions
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 74a9d95..08402d6 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -266,7 +266,7 @@ namespace {
266 }; 266 };
267 267
268 // Read the priority-is-native flag and optional priority integer from the lane_new() argument stack. 268 // Read the priority-is-native flag and optional priority integer from the lane_new() argument stack.
269 // Validates the mapped priority range if native mode is not requested. 269 // Validates the priority against the acceptable range
270 [[nodiscard]] 270 [[nodiscard]]
271 static LanePriority ResolveLanePriority(lua_State* const L_, StackIndex const prinIdx_, StackIndex const prioIdx_) 271 static LanePriority ResolveLanePriority(lua_State* const L_, StackIndex const prinIdx_, StackIndex const prioIdx_)
272 { 272 {
@@ -275,9 +275,16 @@ namespace {
275 return { kThreadPrioDefault, _native }; 275 return { kThreadPrioDefault, _native };
276 } 276 }
277 int const _priority{ static_cast<int>(lua_tointeger(L_, prioIdx_)) }; 277 int const _priority{ static_cast<int>(lua_tointeger(L_, prioIdx_)) };
278 if (!_native && (_priority < kThreadPrioMin || _priority > kThreadPrioMax)) { 278
279 raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority); 279 auto _checkPriorityRange = [L_, _priority](std::pair<int, int> prios_) {
280 } 280 auto const [_prio_min, _prio_max] = prios_;
281 if (_priority < _prio_min || _priority > _prio_max) {
282 raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", _prio_min, _prio_max, _priority);
283 }
284 };
285
286 _checkPriorityRange(_native ? THREAD_NATIVE_PRIOS() : std::pair{ kThreadPrioMin, kThreadPrioMax });
287
281 return { _priority, _native }; 288 return { _priority, _native };
282 } 289 }
283 290
@@ -420,6 +427,7 @@ namespace {
420 static void PrepareLaneUserData(lua_State* const L_, Lane* const lane_, StackIndex const gcCbIdx_, StackIndex const nameIdx_, StackIndex const funcIdx_) 427 static void PrepareLaneUserData(lua_State* const L_, Lane* const lane_, StackIndex const gcCbIdx_, StackIndex const nameIdx_, StackIndex const funcIdx_)
421 { 428 {
422 DEBUGSPEW_CODE(DebugSpew(lane_->U) << "lane_new: preparing lane userdata" << std::endl); 429 DEBUGSPEW_CODE(DebugSpew(lane_->U) << "lane_new: preparing lane userdata" << std::endl);
430 STACK_GROW(L_, 4);
423 STACK_CHECK_START_REL(L_, 0); 431 STACK_CHECK_START_REL(L_, 0);
424 // a Lane full userdata needs a single uservalue 432 // a Lane full userdata needs a single uservalue
425 Lane** const _ud{ luaW_newuserdatauv<Lane*>(L_, UserValueCount{ 1 }) }; // L_: ... lane 433 Lane** const _ud{ luaW_newuserdatauv<Lane*>(L_, UserValueCount{ 1 }) }; // L_: ... lane
@@ -492,155 +500,132 @@ namespace {
492// 500//
493LUAG_FUNC(lane_new) 501LUAG_FUNC(lane_new)
494{ 502{
495 static constexpr StackIndex kFuncIdx{ 1 }; 503 // this is to communicate the lane pointer back to us from inside the protected call
496 static constexpr StackIndex kLibsIdx{ 2 }; 504 Lane* _lane{};
497 static constexpr StackIndex kPrinIdx{ 3 };
498 static constexpr StackIndex kPrioIdx{ 4 };
499 static constexpr StackIndex kGlobIdx{ 5 };
500 static constexpr StackIndex kPackIdx{ 6 };
501 static constexpr StackIndex kRequIdx{ 7 };
502 static constexpr StackIndex kGcCbIdx{ 8 };
503 static constexpr StackIndex kNameIdx{ 9 };
504 static constexpr StackIndex kErTlIdx{ 10 };
505 static constexpr StackIndex kAsCoro{ 11 };
506 static constexpr StackIndex kFixedArgsIdx{ 11 };
507
508 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx };
509 LUA_ASSERT(L_, _nargs >= 0);
510 505
511 Universe* const _U{ Universe::Get(L_) }; 506 auto _protectedLaneNew = [](lua_State* const L_) // stateless lambda convertible to a lua_CFunction
512 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: setup" << std::endl);
513
514 std::optional<std::string_view> _libs_str{ lua_isnil(L_, kLibsIdx) ? std::nullopt : std::make_optional(luaW_tostring(L_, kLibsIdx)) };
515 lua_State* const _S{ state::NewLaneState(_U, SourceState{ L_ }, _libs_str) }; // L_: [fixed] ... L2:
516 STACK_CHECK_START_REL(_S, 0);
517
518 // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread)
519 Lane::ErrorTraceLevel const _errorTraceLevel{ static_cast<Lane::ErrorTraceLevel>(lua_tointeger(L_, kErTlIdx)) };
520 bool const _asCoroutine{ lua_toboolean(L_, kAsCoro) ? true : false };
521 Lane* const _lane{ new (_U) Lane{ _U, _S, _errorTraceLevel, _asCoroutine } };
522 STACK_CHECK(_S, _asCoroutine ? 1 : 0); // the Lane's thread is on the Lane's state stack
523 lua_State* const _L2{ _lane->L };
524 STACK_CHECK_START_REL(_L2, 0);
525 if (_lane == nullptr) {
526 raise_luaL_error(L_, "could not create lane: out of memory");
527 }
528
529 class OnExit final
530 { 507 {
531 private: 508 static constexpr StackIndex kFuncIdx{ 1 };
532 lua_State* const L; 509 static constexpr StackIndex kLibsIdx{ 2 };
533 Lane* lane{ nullptr }; 510 static constexpr StackIndex kPrinIdx{ 3 };
534 DEBUGSPEW_CODE(DebugSpewIndentScope scope); 511 static constexpr StackIndex kLaneIdx{ 3 }; // <-- this is where the Lane userdata is stored (after prio is read from the stack)
535 512 static constexpr StackIndex kPrioIdx{ 4 };
536 public: 513 static constexpr StackIndex kGlobIdx{ 5 };
537 OnExit(lua_State* L_, Lane* lane_) 514 static constexpr StackIndex kPackIdx{ 6 };
538 : L{ L_ } 515 static constexpr StackIndex kRequIdx{ 7 };
539 , lane{ lane_ } 516 static constexpr StackIndex kGcCbIdx{ 8 };
540 DEBUGSPEW_COMMA_PARAM(scope{ lane_->U }) 517 static constexpr StackIndex kNameIdx{ 9 };
541 { 518 static constexpr StackIndex kErTlIdx{ 10 };
519 static constexpr StackIndex kCoroIdx{ 11 };
520 static constexpr StackIndex kFixedArgsIdx{ 11 };
521
522 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx };
523 LUA_ASSERT(L_, _nargs >= 0);
524
525 Universe* const _U{ Universe::Get(L_) };
526 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: setup" << std::endl);
527
528 // Get priority early, because it can fail by raising an error
529 auto const [_priority, _native]{ local::ResolveLanePriority(L_, kPrinIdx, kPrioIdx) };
530
531 std::optional<std::string_view> _libs_str{ lua_isnil(L_, kLibsIdx) ? std::nullopt : std::make_optional(luaW_tostring(L_, kLibsIdx)) };
532 lua_State* const _S{ state::NewLaneState(_U, SourceState{ L_ }, _libs_str) }; // L_: [fixed] ... L2:
533 STACK_CHECK_START_REL(_S, 0);
534
535 Lane::ErrorTraceLevel const _errorTraceLevel{ static_cast<Lane::ErrorTraceLevel>(lua_tointeger(L_, kErTlIdx)) };
536 bool const _asCoroutine{ lua_toboolean(L_, kCoroIdx) ? true : false };
537
538 // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread)
539 Lane* const _lane{ new (_U) Lane{ _U, _S, _errorTraceLevel, _asCoroutine } };
540 if (_lane == nullptr) {
541 raise_luaL_error(L_, "could not create lane: out of memory");
542 } 542 }
543 543 // write back to the outer _lane variable so it can call signalReady()
544 ~OnExit() 544 *static_cast<Lane**>(lua_touserdata(L_, lua_upvalueindex(2))) = _lane;
545 { 545
546 if (lane) { 546 STACK_CHECK(_S, _asCoroutine ? 1 : 0); // the Lane's thread is on the Lane's state stack
547 STACK_CHECK_START_REL(L, 0); 547 lua_State* const _L2{ _lane->L };
548 // we still need a full userdata so that garbage collection can do its thing 548 STACK_CHECK_START_REL(_L2, 0);
549 local::PrepareLaneUserData(L, lane, kGcCbIdx, kNameIdx, kFuncIdx); 549
550 // remove it immediately from the stack so that the error that landed us here is at the top 550 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: launching thread" << std::endl);
551 lua_pop(L, 1); 551
552 STACK_CHECK(L, 0); 552 // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation
553 // leave a single cancel_error on the stack for the caller 553 _lane->startThread(L_, _priority, _native);
554 lua_settop(lane->L, 0); 554
555 kCancelError.pushKey(lane->L); 555 STACK_CHECK_START_REL(L_, 0);
556 { 556
557 std::lock_guard _guard{ lane->doneMutex }; 557 // create the wrapping full userdata, so that it can be cleaned up properly in case of error
558 // this will cause lane_main to skip actual running (because we are not Pending anymore) 558 local::PrepareLaneUserData(L_, _lane, kGcCbIdx, kNameIdx, kFuncIdx); // L_: [fixed] args... LaneUD
559 lane->status.store(Lane::Running, std::memory_order_release); 559 STACK_CHECK(L_, 1);
560 } 560 // store the userdata in a reusable stack slot until we know everything went well
561 // unblock the thread so that it can terminate gracefully 561 lua_replace(L_, kLaneIdx); // L_: [fixed] args...
562#ifndef __PROSPERO__ 562
563 lane->ready.count_down(); 563 STACK_GROW(_L2, _nargs + 3);
564#else // __PROSPERO__ 564 STACK_GROW(L_, 3);
565 lane->ready.test_and_set(); 565
566#endif // __PROSPERO__ 566 // package
567 } 567 local::TransferPackage(_U, L_, _L2, kPackIdx);
568 STACK_CHECK(L_, 0);
569 STACK_CHECK(_L2, 0);
570
571 // modules to require in the target lane *before* the function is transfered!
572 local::RequireModulesInLane(_U, L_, _L2, kRequIdx);
573 STACK_CHECK(L_, 0);
574 STACK_CHECK(_L2, 0); // L_: [fixed] args... L2:
575
576 // Appending the specified globals to the global environment
577 // *after* stdlibs have been loaded and modules required, in case we transfer references to native functions they exposed...
578 local::TransferGlobals(_U, L_, _L2, kGlobIdx);
579 STACK_CHECK(L_, 0);
580 STACK_CHECK(_L2, 0);
581
582 // Lane main function, optionally preceded by an error handler (depending on error_trace_level setting)
583 int const _errorHandlerCount{ local::TransferLaneBody(_U, L_, _L2, kFuncIdx, _lane) }; // L_: [fixed] args... L2: eh? func
584 STACK_CHECK(L_, 0);
585 STACK_CHECK(_L2, _errorHandlerCount + 1);
586 LUA_ASSERT(L_, lua_isfunction(_L2, _errorHandlerCount + 1));
587
588 // *Move* arguments in the lane state
589 local::TransferArguments(_U, L_, _L2, _nargs); // L_: [fixed] L2: eh? func args...
590 STACK_CHECK(L_, -_nargs);
591 LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx);
592 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
593
594 // if in coroutine mode, the Lane's master state stack should contain the thread
595 if (_asCoroutine) {
596 LUA_ASSERT(L_, _S != _L2);
597 STACK_CHECK(_S, 1);
568 } 598 }
599 // and the thread's stack has whatever is needed to run
600 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
569 601
570 public: 602 // all went well, return the LaneUD by cutting the stack at the position we stored it earlier
571 void success() 603 lua_settop(L_, kLaneIdx); // L_: ... lane L2: <living its own life>
572 { 604 return 1;
573 local::PrepareLaneUserData(L, lane, kGcCbIdx, kNameIdx, kFuncIdx); 605 };
574 // unblock the thread so that it can terminate gracefully
575#ifndef __PROSPERO__
576 lane->ready.count_down();
577#else // __PROSPERO__
578 lane->ready.test_and_set();
579#endif // __PROSPERO__
580 lane = nullptr;
581 }
582 } _onExit{ L_, _lane};
583 // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation
584 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: launching thread" << std::endl);
585 // public Lanes API accepts a generic range -3/+3
586 // that will be remapped into the platform-specific scheduler priority scheme
587 // On some platforms, -3 is equivalent to -2 and +3 to +2
588 auto const [_priority, _native]{ local::ResolveLanePriority(L_, kPrinIdx, kPrioIdx) };
589
590 _lane->startThread(L_, _priority, _native);
591
592 STACK_GROW(_L2, _nargs + 3);
593 STACK_GROW(L_, 3);
594 STACK_CHECK_START_REL(L_, 0);
595
596 // package
597 local::TransferPackage(_U, L_, _L2, kPackIdx);
598 STACK_CHECK(L_, 0);
599 STACK_CHECK(_L2, 0);
600
601 // modules to require in the target lane *before* the function is transfered!
602 local::RequireModulesInLane(_U, L_, _L2, kRequIdx);
603 STACK_CHECK(L_, 0);
604 STACK_CHECK(_L2, 0); // L_: [fixed] args... L2:
605
606 // Appending the specified globals to the global environment
607 // *after* stdlibs have been loaded and modules required, in case we transfer references to native functions they exposed...
608 local::TransferGlobals(_U, L_, _L2, kGlobIdx);
609 STACK_CHECK(L_, 0);
610 STACK_CHECK(_L2, 0);
611 606
612 // Lane main function 607 // the protected closure has a 2 uservalues:
613 int const _errorHandlerCount{ local::TransferLaneBody(_U, L_, _L2, kFuncIdx, _lane) }; 608 // - uv#1 is our own uservalue (the Lane userdata's metatable)
614 STACK_CHECK(L_, 0); 609 // - uv#2: a pointer to our local variable _lane, to give us back the Lane pointer
615 STACK_CHECK(_L2, _errorHandlerCount + 1); 610 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: nil [fixed] args... mt
616 LUA_ASSERT(L_, lua_isfunction(_L2, _errorHandlerCount + 1)); 611 lua_pushlightuserdata(L_, &_lane); // L_: nil [fixed] args... mt &_lane
617 612 lua_pushcclosure(L_, _protectedLaneNew, 2); // L_: nil [fixed] args... wrapped
618 // revive arguments 613 lua_replace(L_, 1); // L_: wrapped [fixed] args...
619 local::TransferArguments(_U, L_, _L2, _nargs); 614 LuaError _rc{ lua_pcall(L_, lua_gettop(L_) - 1, 1, 0) }; // L_: lane|error
620 STACK_CHECK(L_, -_nargs); 615 // _lane can be nullptr if we failed to allocate it!
621 LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx); 616 if (_rc == LuaError::OK) {
622 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs); 617 // unblock the thread so that it can start doing its work
623 618 if (_lane) {
624 // Store 'lane' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). 619 _lane->signalReady(true);
625 kLanePointerRegKey.setValue( 620 }
626 _L2, [lane = _lane](lua_State* L_) { lua_pushlightuserdata(L_, lane); } // L_: [fixed] L2: eh? func args... 621 return 1;
627 ); 622 } else {
628 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs); 623 // unblock the thread so that it can terminate gracefully (but will abort early)
629 624 if (_lane) {
630 // if in coroutine mode, the Lane's master state stack should contain the thread 625 _lane->signalReady(false);
631 if (_asCoroutine) { 626 }
632 LUA_ASSERT(L_, _S != _L2); 627 raise_lua_error(L_);
633 STACK_CHECK(_S, 1);
634 } 628 }
635 // and the thread's stack has whatever is needed to run
636 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
637
638 STACK_CHECK_RESET_REL(L_, 0);
639 // all went well, the lane's thread can start working
640 _onExit.success(); // L_: [fixed] lane L2: <living its own life>
641 // we should have the lane userdata on top of the stack
642 STACK_CHECK(L_, 1);
643 return 1;
644} 629}
645 630
646// ################################################################################################# 631// #################################################################################################