diff options
Diffstat (limited to 'src/lanes.cpp')
| -rw-r--r-- | src/lanes.cpp | 277 |
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 | // |
| 493 | LUAG_FUNC(lane_new) | 501 | LUAG_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 | // ################################################################################################# |
