Skip to content

Commit 6224c98

Browse files
GordonSmithCopilot
andcommitted
feat: add recursion check and separate threads table
Align with upstream canonical ABI changes: - Add ComponentInstance::parent for component hierarchy tracking - Add reflexive_ancestors() and is_reflexive_ancestor_of() methods - Add call_might_be_recursive() to detect recursive cross-component calls - Separate thread entries into dedicated ComponentInstance::threads table - Wrap Store::invoke caller in host Supertask with instance=nullptr - Update all thread operations to use threads table instead of table - Add tests for ancestry methods and recursion detection - Bump canonical reference submodules Refs: component-model commits 3a00c74, 5f49720 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d66bc32 commit 6224c98

File tree

6 files changed

+215
-15
lines changed

6 files changed

+215
-15
lines changed

include/cmcpp/context.hpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <thread>
1616
#include <condition_variable>
1717
#include <typeindex>
18+
#include <set>
1819
#include <unordered_map>
1920
#include <vector>
2021
#include <cstring>
@@ -1475,13 +1476,40 @@ namespace cmcpp
14751476
struct ComponentInstance
14761477
{
14771478
Store *store = nullptr;
1479+
ComponentInstance *parent = nullptr;
14781480
bool may_leave = true;
14791481
bool may_enter = true;
14801482
bool exclusive = false;
14811483
uint32_t backpressure = 0;
14821484
uint32_t num_waiting_to_enter = 0;
14831485
HandleTables handles;
14841486
InstanceTable table;
1487+
InstanceTable threads;
1488+
1489+
std::set<const ComponentInstance *> reflexive_ancestors() const
1490+
{
1491+
std::set<const ComponentInstance *> s;
1492+
const ComponentInstance *inst = this;
1493+
while (inst != nullptr)
1494+
{
1495+
s.insert(inst);
1496+
inst = inst->parent;
1497+
}
1498+
return s;
1499+
}
1500+
1501+
bool is_reflexive_ancestor_of(const ComponentInstance *other) const
1502+
{
1503+
while (other != nullptr)
1504+
{
1505+
if (this == other)
1506+
{
1507+
return true;
1508+
}
1509+
other = other->parent;
1510+
}
1511+
return false;
1512+
}
14851513
};
14861514

14871515
inline void ensure_may_leave(ComponentInstance &inst, const HostTrap &trap)
@@ -1509,6 +1537,40 @@ namespace cmcpp
15091537
inst.backpressure -= 1;
15101538
}
15111539

1540+
inline bool call_might_be_recursive(const SupertaskPtr &caller, const ComponentInstance *callee_inst)
1541+
{
1542+
if (!caller || !callee_inst)
1543+
{
1544+
return false;
1545+
}
1546+
if (caller->instance == nullptr)
1547+
{
1548+
auto current = caller.get();
1549+
while (current != nullptr)
1550+
{
1551+
if (current->instance)
1552+
{
1553+
auto caller_ancestors = current->instance->reflexive_ancestors();
1554+
auto callee_ancestors = callee_inst->reflexive_ancestors();
1555+
for (auto *a : caller_ancestors)
1556+
{
1557+
if (callee_ancestors.count(a))
1558+
{
1559+
return true;
1560+
}
1561+
}
1562+
}
1563+
current = current->parent.get();
1564+
}
1565+
return false;
1566+
}
1567+
else
1568+
{
1569+
return caller->instance->is_reflexive_ancestor_of(callee_inst) ||
1570+
callee_inst->is_reflexive_ancestor_of(caller->instance);
1571+
}
1572+
}
1573+
15121574
class Task : public std::enable_shared_from_this<Task>
15131575
{
15141576
public:
@@ -1796,7 +1858,7 @@ namespace cmcpp
17961858
trap_if(trap_cx, inst == nullptr, "thread.resume-later missing component instance");
17971859
ensure_may_leave(*inst, trap);
17981860

1799-
auto entry = inst->table.get<ThreadEntry>(thread_index, trap);
1861+
auto entry = inst->threads.get<ThreadEntry>(thread_index, trap);
18001862
auto other_thread = entry->thread();
18011863
trap_if(trap_cx, !other_thread, "thread.resume-later null thread");
18021864
trap_if(trap_cx, !other_thread->suspended(), "thread not suspended");
@@ -1810,7 +1872,7 @@ namespace cmcpp
18101872
trap_if(trap_cx, inst == nullptr, "thread.yield-to missing component instance");
18111873
ensure_may_leave(*inst, trap);
18121874

1813-
auto entry = inst->table.get<ThreadEntry>(thread_index, trap);
1875+
auto entry = inst->threads.get<ThreadEntry>(thread_index, trap);
18141876
auto other_thread = entry->thread();
18151877
trap_if(trap_cx, !other_thread, "thread.yield-to null thread");
18161878
trap_if(trap_cx, !other_thread->suspended(), "thread not suspended");
@@ -1836,7 +1898,7 @@ namespace cmcpp
18361898
trap_if(trap_cx, inst == nullptr, "thread.switch-to missing component instance");
18371899
ensure_may_leave(*inst, trap);
18381900

1839-
auto entry = inst->table.get<ThreadEntry>(thread_index, trap);
1901+
auto entry = inst->threads.get<ThreadEntry>(thread_index, trap);
18401902
auto other_thread = entry->thread();
18411903
trap_if(trap_cx, !other_thread, "thread.switch-to null thread");
18421904
trap_if(trap_cx, !other_thread->suspended(), "thread not suspended");
@@ -1881,7 +1943,7 @@ namespace cmcpp
18811943
{});
18821944
trap_if(trap_cx, !thread || !thread->suspended(), "thread.new-ref failed to create suspended thread");
18831945

1884-
uint32_t index = inst->table.add(std::make_shared<ThreadEntry>(thread), trap);
1946+
uint32_t index = inst->threads.add(std::make_shared<ThreadEntry>(thread), trap);
18851947
thread->set_index(index);
18861948
return index;
18871949
}

include/cmcpp/runtime.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,10 @@ namespace cmcpp
443443
{
444444
return Call();
445445
}
446-
return func(*this, std::move(caller), std::move(on_start), std::move(on_resolve));
446+
auto host_caller = std::make_shared<Supertask>();
447+
host_caller->instance = nullptr;
448+
host_caller->parent = std::move(caller);
449+
return func(*this, std::move(host_caller), std::move(on_start), std::move(on_resolve));
447450
}
448451

449452
inline void Store::tick()

ref/wasm-micro-runtime

Submodule wasm-micro-runtime updated 262 files

ref/wit-bindgen

Submodule wit-bindgen updated 427 files

test/main.cpp

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ TEST_CASE("thread.yield-to resumes suspended thread")
800800
true,
801801
{});
802802
REQUIRE(other->suspended());
803-
uint32_t other_index = inst.table.add(std::make_shared<ThreadEntry>(other), trap);
803+
uint32_t other_index = inst.threads.add(std::make_shared<ThreadEntry>(other), trap);
804804

805805
auto task = std::make_shared<Task>(inst, async_opts);
806806
auto current = Thread::create(
@@ -855,7 +855,7 @@ TEST_CASE("thread.yield-to traps if target not suspended")
855855
},
856856
true,
857857
{});
858-
uint32_t other_index = inst.table.add(std::make_shared<ThreadEntry>(other), trap);
858+
uint32_t other_index = inst.threads.add(std::make_shared<ThreadEntry>(other), trap);
859859

860860
auto task = std::make_shared<Task>(inst, async_opts);
861861
auto current = Thread::create(
@@ -912,7 +912,7 @@ TEST_CASE("thread.new-indirect creates a suspended thread")
912912
[task, &inst, &trap, &table, ran](bool)
913913
{
914914
uint32_t idx = canon_thread_new_indirect(false, *task, table, 1, 123, trap);
915-
auto entry = inst.table.get<ThreadEntry>(idx, trap);
915+
auto entry = inst.threads.get<ThreadEntry>(idx, trap);
916916
REQUIRE(entry->thread());
917917
CHECK(entry->thread()->suspended());
918918

@@ -964,7 +964,7 @@ TEST_CASE("thread.new-ref and spawn-ref create threads")
964964
{
965965
CHECK(c == 7u);
966966
*ran_new = true; }, 7, trap);
967-
auto new_entry = inst.table.get<ThreadEntry>(idx_new, trap);
967+
auto new_entry = inst.threads.get<ThreadEntry>(idx_new, trap);
968968
REQUIRE(new_entry->thread());
969969
CHECK(new_entry->thread()->suspended());
970970
canon_thread_resume_later(*task, idx_new, trap);
@@ -973,7 +973,7 @@ TEST_CASE("thread.new-ref and spawn-ref create threads")
973973
{
974974
CHECK(c == 9u);
975975
*ran_spawn = true; }, 9, trap);
976-
auto spawn_entry = inst.table.get<ThreadEntry>(idx_spawn, trap);
976+
auto spawn_entry = inst.threads.get<ThreadEntry>(idx_spawn, trap);
977977
REQUIRE(spawn_entry->thread());
978978
CHECK_FALSE(spawn_entry->thread()->suspended());
979979
return false;
@@ -1025,7 +1025,7 @@ TEST_CASE("thread.spawn-indirect resumes and runs")
10251025
[task, &inst, &trap, &table](bool)
10261026
{
10271027
uint32_t idx = canon_thread_spawn_indirect(false, *task, table, 1, 55, trap);
1028-
auto entry = inst.table.get<ThreadEntry>(idx, trap);
1028+
auto entry = inst.threads.get<ThreadEntry>(idx, trap);
10291029
REQUIRE(entry->thread());
10301030
CHECK_FALSE(entry->thread()->suspended());
10311031
return false;
@@ -1089,7 +1089,7 @@ TEST_CASE("thread.switch-to schedules a suspended thread")
10891089
},
10901090
true,
10911091
{});
1092-
uint32_t other_index = inst.table.add(std::make_shared<ThreadEntry>(other), trap);
1092+
uint32_t other_index = inst.threads.add(std::make_shared<ThreadEntry>(other), trap);
10931093

10941094
auto task = std::make_shared<Task>(inst, async_opts);
10951095
auto current = Thread::create(
@@ -1362,6 +1362,141 @@ TEST_CASE("InstanceContext wires canonical options")
13621362
CHECK(observed_events.back().payload == 0xCAFE'BEEFu);
13631363
}
13641364

1365+
TEST_CASE("ComponentInstance reflexive_ancestors returns self and parents")
1366+
{
1367+
Store store;
1368+
ComponentInstance root;
1369+
root.store = &store;
1370+
1371+
ComponentInstance child;
1372+
child.store = &store;
1373+
child.parent = &root;
1374+
1375+
ComponentInstance grandchild;
1376+
grandchild.store = &store;
1377+
grandchild.parent = &child;
1378+
1379+
auto root_ancestors = root.reflexive_ancestors();
1380+
CHECK(root_ancestors.size() == 1);
1381+
CHECK(root_ancestors.count(&root) == 1);
1382+
1383+
auto child_ancestors = child.reflexive_ancestors();
1384+
CHECK(child_ancestors.size() == 2);
1385+
CHECK(child_ancestors.count(&child) == 1);
1386+
CHECK(child_ancestors.count(&root) == 1);
1387+
1388+
auto gc_ancestors = grandchild.reflexive_ancestors();
1389+
CHECK(gc_ancestors.size() == 3);
1390+
CHECK(gc_ancestors.count(&grandchild) == 1);
1391+
CHECK(gc_ancestors.count(&child) == 1);
1392+
CHECK(gc_ancestors.count(&root) == 1);
1393+
}
1394+
1395+
TEST_CASE("ComponentInstance is_reflexive_ancestor_of checks ancestry")
1396+
{
1397+
Store store;
1398+
ComponentInstance root;
1399+
root.store = &store;
1400+
1401+
ComponentInstance child;
1402+
child.store = &store;
1403+
child.parent = &root;
1404+
1405+
ComponentInstance grandchild;
1406+
grandchild.store = &store;
1407+
grandchild.parent = &child;
1408+
1409+
ComponentInstance unrelated;
1410+
unrelated.store = &store;
1411+
1412+
CHECK(root.is_reflexive_ancestor_of(&root));
1413+
CHECK(root.is_reflexive_ancestor_of(&child));
1414+
CHECK(root.is_reflexive_ancestor_of(&grandchild));
1415+
CHECK(child.is_reflexive_ancestor_of(&child));
1416+
CHECK(child.is_reflexive_ancestor_of(&grandchild));
1417+
CHECK_FALSE(child.is_reflexive_ancestor_of(&root));
1418+
CHECK_FALSE(grandchild.is_reflexive_ancestor_of(&root));
1419+
CHECK_FALSE(root.is_reflexive_ancestor_of(&unrelated));
1420+
CHECK_FALSE(unrelated.is_reflexive_ancestor_of(&root));
1421+
}
1422+
1423+
TEST_CASE("call_might_be_recursive detects same-instance recursion")
1424+
{
1425+
Store store;
1426+
ComponentInstance inst;
1427+
inst.store = &store;
1428+
1429+
auto caller = std::make_shared<Supertask>();
1430+
caller->instance = &inst;
1431+
1432+
CHECK(call_might_be_recursive(caller, &inst));
1433+
}
1434+
1435+
TEST_CASE("call_might_be_recursive detects ancestor recursion")
1436+
{
1437+
Store store;
1438+
ComponentInstance root;
1439+
root.store = &store;
1440+
1441+
ComponentInstance child;
1442+
child.store = &store;
1443+
child.parent = &root;
1444+
1445+
auto caller = std::make_shared<Supertask>();
1446+
caller->instance = &root;
1447+
1448+
CHECK(call_might_be_recursive(caller, &child));
1449+
CHECK(call_might_be_recursive(caller, &root));
1450+
1451+
auto child_caller = std::make_shared<Supertask>();
1452+
child_caller->instance = &child;
1453+
1454+
CHECK(call_might_be_recursive(child_caller, &root));
1455+
}
1456+
1457+
TEST_CASE("call_might_be_recursive returns false for unrelated instances")
1458+
{
1459+
Store store;
1460+
ComponentInstance inst_a;
1461+
inst_a.store = &store;
1462+
1463+
ComponentInstance inst_b;
1464+
inst_b.store = &store;
1465+
1466+
auto caller = std::make_shared<Supertask>();
1467+
caller->instance = &inst_a;
1468+
1469+
CHECK_FALSE(call_might_be_recursive(caller, &inst_b));
1470+
}
1471+
1472+
TEST_CASE("call_might_be_recursive walks host caller chain")
1473+
{
1474+
Store store;
1475+
ComponentInstance inst;
1476+
inst.store = &store;
1477+
1478+
// Simulate host caller (instance == nullptr) with a parent that has an instance
1479+
auto inner = std::make_shared<Supertask>();
1480+
inner->instance = &inst;
1481+
1482+
auto host_caller = std::make_shared<Supertask>();
1483+
host_caller->instance = nullptr;
1484+
host_caller->parent = inner;
1485+
1486+
CHECK(call_might_be_recursive(host_caller, &inst));
1487+
1488+
// Unrelated instance should return false
1489+
ComponentInstance other;
1490+
other.store = &store;
1491+
CHECK_FALSE(call_might_be_recursive(host_caller, &other));
1492+
}
1493+
1494+
TEST_CASE("call_might_be_recursive returns false for null caller")
1495+
{
1496+
ComponentInstance inst;
1497+
CHECK_FALSE(call_might_be_recursive(nullptr, &inst));
1498+
}
1499+
13651500
TEST_CASE("Canonical callbacks surface waitable events")
13661501
{
13671502
ComponentInstance inst;

0 commit comments

Comments
 (0)