From 3060ac5b7b92aea17a0e6b9c5815d9b0bb8b8c61 Mon Sep 17 00:00:00 2001 From: Rich McKeever Date: Mon, 22 Jun 2026 11:40:12 -0700 Subject: [PATCH] Get imported ProcDefs working in IR conversion. The main issue was that the ProcDef logic in GetConversionRecords was missing the part for traversing spawned foreign procs; this logic was always there for legacy procs. PiperOrigin-RevId: 936167457 --- xls/dslx/get_conversion_records.cc | 23 ++++++ xls/dslx/ir_convert/function_converter.cc | 2 +- xls/dslx/ir_convert/ir_converter_test.cc | 69 ++++++++++++++++ .../ir_converter_test_SpawnImportedProcDef.ir | 81 +++++++++++++++++++ .../typecheck_module_v2_proc_test.cc | 60 ++++++++++++++ 5 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 xls/dslx/ir_convert/testdata/ir_converter_test_SpawnImportedProcDef.ir diff --git a/xls/dslx/get_conversion_records.cc b/xls/dslx/get_conversion_records.cc index bf5b356d67..08bc85e580 100644 --- a/xls/dslx/get_conversion_records.cc +++ b/xls/dslx/get_conversion_records.cc @@ -390,6 +390,28 @@ class ConversionRecordVisitor : public AstNodeVisitorWithDefault { VLOG(5) << "No calls to parametric proc " << p->name_def()->ToString(); return absl::OkStatus(); } + + XLS_ASSIGN_OR_RETURN(std::vector spawns, + proc_owner_ti->GetProcDefSpawnsFrom(p)); + for (const InterpValue& external_initializer : spawns) { + const ProcDef* spawnee = + external_initializer.GetProcInitializerOrDie().proc_def(); + if (spawnee->owner() == p->owner()) { + continue; + } + + // Proc is outside this module; get additional conversion records from + // its spawning and add to our list of records. + XLS_ASSIGN_OR_RETURN( + ProcInitializerWithTypeInfo canonical_initializer, + proc_owner_ti->GetCanonicalProcInitializer(external_initializer)); + ConversionRecordVisitor visitor( + spawnee->owner(), canonical_initializer.next_type_info, + include_tests_, proc_id_factory_, top_, resolved_proc_alias_, + records_, processed_invocations_); + XLS_RETURN_IF_ERROR(spawnee->Accept(&visitor)); + } + for (const ProcInitializerWithTypeInfo& canonical_initializer : canonical_initializers) { // TODO: https://github.com/google/xls/issues/4125 - Exclude test-only @@ -409,6 +431,7 @@ class ConversionRecordVisitor : public AstNodeVisitorWithDefault { canonical_initializer)); records_.push_back(std::move(cr)); } + return absl::OkStatus(); } diff --git a/xls/dslx/ir_convert/function_converter.cc b/xls/dslx/ir_convert/function_converter.cc index ed71c27a42..bd95cfbbcb 100644 --- a/xls/dslx/ir_convert/function_converter.cc +++ b/xls/dslx/ir_convert/function_converter.cc @@ -3749,7 +3749,7 @@ absl::Status FunctionConverter::InitProcDefBuilder(const ProcDef* proc_def, XLS_ASSIGN_OR_RETURN( std::string mangled_name, - MangleDslxName(module_->name(), proc_def->identifier(), + MangleDslxName(proc_def->owner()->name(), proc_def->identifier(), CallingConvention::kProcNext, parametric_keys, &env)); auto unique_builder = std::make_unique(NewStyleProc{}, mangled_name, package()); diff --git a/xls/dslx/ir_convert/ir_converter_test.cc b/xls/dslx/ir_convert/ir_converter_test.cc index 0563e716ad..a3fab9a2a0 100644 --- a/xls/dslx/ir_convert/ir_converter_test.cc +++ b/xls/dslx/ir_convert/ir_converter_test.cc @@ -2679,6 +2679,75 @@ impl Main { ExpectIr(converted); } +TEST_F(IrConverterTest, SpawnImportedProcDef) { + auto import_data = CreateImportDataForTest(); + + std::string_view kImported = R"( +#![feature(explicit_state_access)] + +pub proc P { + c_out: chan out, + i: u32, +} + +impl P { + fn new(c_out: chan out) -> Self { + P { c_out: c_out, i: 0 } + } + + fn next(self) { + let last_i = read(self.i); + send(join(), self.c_out, last_i); + write(self.i, last_i + 1); + } +} +)"; + + XLS_EXPECT_OK( + ParseAndTypecheck(kImported, "imported.x", "imported", &import_data)); + + constexpr std::string_view kProgram = R"( +#![feature(explicit_state_access)] + +import imported; + +proc C { + c_in: chan in, + i: u32, +} + +impl C { + fn new(c_in: chan in) -> Self { + C { c_in: c_in, i: 0 } + } + fn next(self) { + let last_i = read(self.i); + let (tok1, e) = recv(join(), self.c_in); + write(self.i, e + last_i); + } +} + +proc Main {} + +impl Main { + fn new() -> Self { + let (c_out, c_in) = chan("my_chan"); + imported::P::new(c_out).spawn(); + let c = C::new(c_in); + c.spawn(); + Main {} + } + + fn next(self) {} +} +)"; + + XLS_ASSERT_OK_AND_ASSIGN( + std::string converted, + ConvertModuleForTest(kProgram, kProcScopedChannelOptions, &import_data)); + ExpectIr(converted); +} + TEST_F(IrConverterTest, SendIfRecvIf) { constexpr std::string_view program = R"(proc producer { c: chan out; diff --git a/xls/dslx/ir_convert/testdata/ir_converter_test_SpawnImportedProcDef.ir b/xls/dslx/ir_convert/testdata/ir_converter_test_SpawnImportedProcDef.ir new file mode 100644 index 0000000000..388d81b270 --- /dev/null +++ b/xls/dslx/ir_convert/testdata/ir_converter_test_SpawnImportedProcDef.ir @@ -0,0 +1,81 @@ +package test_module + +file_number 0 "test_module.x" +file_number 1 "imported.x" + +proc __imported__P_next<_c_out: bits[32] out>(__i: bits[32], init={0}) { + chan_interface _c_out(direction=send, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) + literal.32: bits[1] = literal(value=1, id=32) + literal.33: bits[1] = literal(value=0, id=33) + not.36: bits[1] = not(literal.32, id=36) + not.37: bits[1] = not(literal.33, id=37) + __token: token = literal(value=token, id=31) + or.38: bits[1] = or(not.36, not.37, id=38) + not.48: bits[1] = not(literal.32, id=48) + or.40: bits[1] = or(literal.33, literal.32, id=40) + literal.34: bits[1] = literal(value=0, id=34) + __i__1: bits[32] = state_read(state_element=__i, predicate=literal.32, id=41) + assert.39: token = assert(__token, or.38, message="State element read after read in same activation.", id=39) + or.49: bits[1] = or(not.48, or.40, id=49) + not.51: bits[1] = not(literal.32, id=51) + not.52: bits[1] = not(literal.34, id=52) + last_i: bits[32] = identity(__i__1, id=42) + literal.46: bits[32] = literal(value=1, id=46) + after_all.44: token = after_all(id=44) + assert.50: token = assert(assert.39, or.49, message="State element written before read in same activation.", id=50) + or.53: bits[1] = or(not.51, not.52, id=53) + __i: bits[32] = state_read(state_element=__i, id=35) + add.47: bits[32] = add(last_i, literal.46, id=47) + or.43: bits[1] = or(literal.33, literal.32, id=43) + send.45: token = send(after_all.44, last_i, predicate=literal.32, channel=_c_out, id=45) + assert.54: token = assert(assert.50, or.53, message="State element written after write in same activation.", id=54) + or.55: bits[1] = or(literal.34, literal.32, id=55) + next_value.56: () = next_value(param=__i, value=add.47, predicate=literal.32, id=56) + tuple.57: () = tuple(id=57) + tuple.58: () = tuple(id=58) +} + +proc __test_module__C_next<_c_in: bits[32] in>(__i: bits[32], init={0}) { + chan_interface _c_in(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=ready_valid, flop_kind=none) + literal.2: bits[1] = literal(value=1, id=2) + literal.3: bits[1] = literal(value=0, id=3) + not.6: bits[1] = not(literal.2, id=6) + not.7: bits[1] = not(literal.3, id=7) + after_all.14: token = after_all(id=14) + __token: token = literal(value=token, id=1) + or.8: bits[1] = or(not.6, not.7, id=8) + not.20: bits[1] = not(literal.2, id=20) + or.10: bits[1] = or(literal.3, literal.2, id=10) + literal.4: bits[1] = literal(value=0, id=4) + receive.15: (token, bits[32]) = receive(after_all.14, predicate=literal.2, channel=_c_in, id=15) + __i__1: bits[32] = state_read(state_element=__i, predicate=literal.2, id=11) + assert.9: token = assert(__token, or.8, message="State element read after read in same activation.", id=9) + or.21: bits[1] = or(not.20, or.10, id=21) + not.23: bits[1] = not(literal.2, id=23) + not.24: bits[1] = not(literal.4, id=24) + e: bits[32] = tuple_index(receive.15, index=1, id=18) + last_i: bits[32] = identity(__i__1, id=12) + assert.22: token = assert(assert.9, or.21, message="State element written before read in same activation.", id=22) + or.25: bits[1] = or(not.23, not.24, id=25) + __i: bits[32] = state_read(state_element=__i, id=5) + add.19: bits[32] = add(e, last_i, id=19) + or.13: bits[1] = or(literal.3, literal.2, id=13) + tuple_index.16: token = tuple_index(receive.15, index=0, id=16) + tok1: token = tuple_index(receive.15, index=0, id=17) + assert.26: token = assert(assert.22, or.25, message="State element written after write in same activation.", id=26) + or.27: bits[1] = or(literal.4, literal.2, id=27) + next_value.28: () = next_value(param=__i, value=add.19, predicate=literal.2, id=28) + tuple.29: () = tuple(id=29) + tuple.30: () = tuple(id=30) +} + +proc __test_module__Main_next<>() { + chan _my_chan(bits[32], id=0, kind=streaming, ops=send_receive, flow_control=ready_valid, strictness=proven_mutually_exclusive) + chan_interface _my_chan(direction=send, kind=streaming, strictness=proven_mutually_exclusive, flow_control=none, flop_kind=none) + chan_interface _my_chan(direction=receive, kind=streaming, strictness=proven_mutually_exclusive, flow_control=none, flop_kind=none) + proc_instantiation __imported__P_next_inst(_my_chan, proc=__imported__P_next) + proc_instantiation __test_module__C_next_inst(_my_chan, proc=__test_module__C_next) + __token: token = literal(value=token, id=59) + literal.60: bits[1] = literal(value=1, id=60) + tuple.61: () = tuple(id=61) +} diff --git a/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc b/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc index fd726a0171..7d16a2ee72 100644 --- a/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc +++ b/xls/dslx/type_system_v2/typecheck_module_v2_proc_test.cc @@ -647,5 +647,65 @@ impl Loopback { "chan(uN[32], dir=out) }) -> ()"))); } +TEST(TypecheckV2Test, SpawnImportedProcDef) { + std::string_view kImported = R"( +#![feature(explicit_state_access)] + +pub proc P { + c_out: chan out, + i: u32, +} + +impl P { + fn new(c_out: chan out) -> Self { + P { c_out: c_out, i: 0 } + } + + fn next(self) { + let last_i = read(self.i); + send(join(), self.c_out, last_i); + write(self.i, last_i + 1); + } +} +)"; + + constexpr std::string_view kProgram = R"( +#![feature(explicit_state_access)] + +import imported; +proc C { + c_in: chan in, + i: u32, +} + +impl C { + fn new(c_in: chan in) -> Self { + C { c_in: c_in, i: 0 } + } + fn next(self) { + let last_i = read(self.i); + let (tok1, e) = recv(join(), self.c_in); + write(self.i, e + last_i); + } +} + +proc Main {} + +impl Main { + fn new() -> Self { + let (c_out, c_in) = chan("my_chan"); + imported::P::new(c_out).spawn(); + let c = C::new(c_in); + c.spawn(); + Main {} + } +} +)"; + + ImportData import_data = CreateImportDataForTest(); + XLS_EXPECT_OK(TypecheckV2(kImported, "imported", &import_data).status()); + XLS_EXPECT_OK(TypecheckV2(kProgram, "main", &import_data)); +} + } // namespace } // namespace xls::dslx