diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..4e1489b8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# Project Instructions + +- Before compiler changes, read the relevant section of the [compiler design reference](../doc/compiler-design-reference.md). Preserve the Haskell frontend -> ParseTree FFI -> C++ middle end -> CIRCT/SystemVerilog backend ownership boundaries. +- Keep Haskell/C++ ParseTree FFI declarations, implementations, and bindings synchronized. Preserve source-location and type metadata when adding or translating nodes. +- Use the [programming guide](../doc/programming-guide.md), [effective Kanagawa](../doc/effective-kanagawa.md), and [hardware mapping guide](../doc/mapping-to-hardware.md) as the authorities for language semantics; do not infer semantics from a single test or implementation detail. +- Follow [BUILDING.md](../BUILDING.md) for dependencies, out-of-tree CMake/Ninja builds, and test targets. Build the smallest relevant target and run the narrowest matching `ctest -R` group or single test. +- Full tests are supported on Linux or WSL. On native Windows, build the compiler and related tools, but do not claim full test coverage. +- Treat `thirdparty/` as vendored code and avoid changing it unless the task explicitly targets a dependency. diff --git a/compiler/cpp/circt_util.cpp b/compiler/cpp/circt_util.cpp index 21ce0ab8..72764fa3 100644 --- a/compiler/cpp/circt_util.cpp +++ b/compiler/cpp/circt_util.cpp @@ -502,7 +502,13 @@ circt::hw::ArrayType GetPackedArrayTypeParameterizedSize(const mlir::Type &eleme circt::seq::ClockType GetClockType() { return circt::seq::ClockType::get(g_compiler->GetMlirContext()); } -mlir::Type ToMlirType(const Type *typeIn, bool signedness) +// Internal helper: converts a Kanagawa Type to an MLIR type, using the provided +// 'recurse' callback for recursive member/element type conversion. +// This avoids duplicating struct/union/array lowering logic between +// ToMlirType and ToMlirTypeAliased. +using TypeRecurseFn = std::function; + +static mlir::Type ToMlirTypeImpl(const Type *typeIn, bool signedness, const TypeRecurseFn &recurse) { const BoolType *boolType = dynamic_cast(typeIn); const ArrayType *arrayType = dynamic_cast(typeIn); @@ -516,7 +522,7 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) } else if (arrayType) { - return GetPackedArrayType(ToMlirType(arrayType->_elementType, signedness), arrayType->_arraySize); + return GetPackedArrayType(recurse(arrayType->_elementType, signedness), arrayType->_arraySize); } else if (floatType) { @@ -528,17 +534,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) { llvm::SmallVector fields; - const auto addField = [&](const std::string &name, const Type *const type) - { - fields.push_back( - circt::hw::StructType::FieldInfo{StringToStringAttr(name), ToMlirType(type, signedness)}); - }; - for (const StructUnionType::EntryType &member : structUnionType->_members) { const Type *const memberType = member.second->GetDeclaredType(); - const std::string memberName = member.first; - addField(memberName, memberType); + fields.push_back( + circt::hw::StructType::FieldInfo{StringToStringAttr(member.first), recurse(memberType, signedness)}); } std::reverse(fields.begin(), fields.end()); @@ -548,17 +548,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) { llvm::SmallVector fields; - const auto addField = [&](const std::string &name, const Type *const type) - { - fields.push_back( - circt::hw::UnionType::FieldInfo{StringToStringAttr(name), ToMlirType(type, signedness)}); - }; - for (const StructUnionType::EntryType &member : structUnionType->_members) { const Type *const memberType = member.second->GetDeclaredType(); - const std::string memberName = member.first; - addField(memberName, memberType); + fields.push_back( + circt::hw::UnionType::FieldInfo{StringToStringAttr(member.first), recurse(memberType, signedness)}); } std::reverse(fields.begin(), fields.end()); @@ -584,6 +578,25 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) } } +mlir::Type ToMlirType(const Type *typeIn, bool signedness) +{ + return ToMlirTypeImpl(typeIn, signedness, [](const Type *t, bool s) { return ToMlirType(t, s); }); +} + +mlir::Type ToMlirTypeAliased(const Type *typeIn, bool signedness, ModuleDeclarationHelper &helper) +{ + // Check if this type has a registered alias + auto alias = helper.GetTypeAlias(typeIn); + if (alias) + { + return *alias; + } + + // Delegate to the shared implementation with alias-aware recursion + return ToMlirTypeImpl(typeIn, signedness, + [&helper](const Type *t, bool s) { return ToMlirTypeAliased(t, s, helper); }); +} + // Used to avoid symbol name conflicts for elements like container ports // returns a symbol name which will be unique provided // that flattened container paths are unique @@ -1728,7 +1741,7 @@ void ModuleDeclarationHelper::AssertStructsMatch(const mlir::Type &circtTypeAlia _verbatimBuffer.Str() << "end"; } -mlir::Type ModuleDeclarationHelper::GetTypeAlias(const std::string &name, const mlir::Type &referencedType) +mlir::Type ModuleDeclarationHelper::CreateTypeAlias(const std::string &name, const mlir::Type &referencedType) { // AddTypedefs must be called first assert(_typeScopeOp); @@ -1739,10 +1752,142 @@ mlir::Type ModuleDeclarationHelper::GetTypeAlias(const std::string &name, const return circt::hw::TypeAliasType::get(symbolRefAttr, referencedType); } +// Returns true if 'kanagawaType' (and all of its transitively contained +// element/member types) can be lowered to MLIR by ToMlirTypeImpl. +// Non-hardware types (StringType, ClassType, ReferenceType, callback function +// members, etc.) are not lowerable and would trigger an assert(false) if +// reached during type lowering. +static bool IsMlirLowerable(const Type *kanagawaType) +{ + if (dynamic_cast(kanagawaType) || dynamic_cast(kanagawaType) || + dynamic_cast(kanagawaType)) + { + return true; + } + + if (const ArrayType *arrayType = dynamic_cast(kanagawaType)) + { + return IsMlirLowerable(arrayType->_elementType); + } + + if (const StructUnionType *structUnionType = dynamic_cast(kanagawaType)) + { + for (const StructUnionType::EntryType &member : structUnionType->_members) + { + if (!IsMlirLowerable(member.second->GetDeclaredType())) + { + return false; + } + } + return true; + } + + return false; +} + +void ModuleDeclarationHelper::RegisterNamedType(const Type *kanagawaType) +{ + assert(_typeScopeOp); + + // Skip if already registered + if (_typeAliasCache.count(kanagawaType)) + { + return; + } + + const StructUnionType *structUnionType = dynamic_cast(kanagawaType); + const EnumType *enumType = dynamic_cast(kanagawaType); + + std::string typeName; + if (structUnionType) + { + typeName = structUnionType->GetName(); + } + else if (enumType) + { + typeName = enumType->GetName(); + } + + // Only register types with a non-empty name. + if (typeName.empty()) + { + return; + } + + // Kanagawa type names typically contain '.' from namespacing. + // Normalize identifier the same way the SV backend does. + typeName = FixupString(typeName); + + // Check if a different Type* with the same name was already registered. + // Reuse the existing alias to prevent duplicate hw.typedecl symbols, + // but verify the underlying layout matches to catch conflicting definitions. + auto nameIt = _typeAliasByName.find(typeName); + if (nameIt != _typeAliasByName.end()) + { + circt::hw::TypeAliasType existingAlias = llvm::cast(nameIt->second); + mlir::Type newMlirType = ToMlirTypeAliased(kanagawaType, true, *this); + if (existingAlias.getInnerType() != newMlirType) + { + throw std::runtime_error("Conflicting named type definitions for '" + typeName + + "': existing alias has a different underlying layout"); + } + _typeAliasCache[kanagawaType] = nameIt->second; + return; + } + + // Note: we do NOT recursively register member types here. + // The caller (DeclareCore) iterates _exportedTypes which is already + // topologically sorted by SortExportedTypes(), so member types are + // registered before their containing structs. That ordering is what + // lets ToMlirTypeAliased below pick up aliases for inner named types. + // If a member was skipped due to not being MLIR-lowerable, the containing type + // will also be skipped. + + // Verify the type can be converted to MLIR before attempting registration. + // Some exported types (e.g., structs containing callback function members, + // or structs containing strings) have members that ToMlirType cannot handle. + // The check must be transitive: if any nested member/element is not + // MLIR-lowerable, skip registration. Otherwise we'd register an outer + // struct whose inner struct was skipped, and ToMlirTypeAliased would + // recurse into the inner struct's non-hardware members and assert. + if (!IsMlirLowerable(kanagawaType)) + { + return; + } + + // Build the MLIR type using the alias-aware conversion so inner named types use aliases. + // Use signedness=true to match the ESI wrapper consumer (the only caller of ToMlirTypeAliased), + // which passes signedness=true to produce signed/unsigned integer types. + mlir::Type mlirType = ToMlirTypeAliased(kanagawaType, true, *this); + + // Create the TypedeclOp in the TypeScope block + { + circt::OpBuilder::InsertionGuard g(_opb); + _opb.setInsertionPointToEnd(_typeScopeOp.getBodyBlock()); + circt::hw::TypedeclOp::create(_opb, _location, StringToStringAttr(typeName), mlirType, + StringToStringAttr(typeName)); + } + + // Create and cache the type alias + mlir::Type aliasType = CreateTypeAlias(typeName, mlirType); + _typeAliasCache[kanagawaType] = aliasType; + _typeAliasByName[typeName] = aliasType; +} + +std::optional ModuleDeclarationHelper::GetTypeAlias(const Type *kanagawaType) const +{ + auto it = _typeAliasCache.find(kanagawaType); + if (it != _typeAliasCache.end()) + { + return it->second; + } + return std::nullopt; +} + mlir::Type ModuleDeclarationHelper::GetInspectableTypeAlias() { assert(GetCodeGenConfig()._inspection); - return GetTypeAlias(InspectableValueName, GetInspectableStructType()); + return CreateTypeAlias(InspectableValueName, GetInspectableStructType()); } mlir::ModuleOp ModuleDeclarationHelper::MlirModule() { return _mlirModule; } @@ -1913,7 +2058,7 @@ void ModuleDeclarationHelper::EmitEsiWrapper(const std::string &circtDesignName) break; case EsiPortSemantics::Payload: - bundlePayloadTypes.push_back(ToMlirType(portInfo._origType, true)); + bundlePayloadTypes.push_back(ToMlirTypeAliased(portInfo._origType, true, *this)); payloadTypes.push_back(portInfo._hwPortInfo.type); payloadNames.push_back(portInfo._hwPortInfo.name.str()); payloadFieldNames.push_back(portInfo._fieldName); @@ -2171,7 +2316,7 @@ void ModuleDeclarationHelper::EmitEsiWrapper(const std::string &circtDesignName) payload.push_back(ReadContainerPort( _opb, _location, pathToContainer, GetFullyQualifiedStringAttr(ObjectPath(), portInfo._hwPortInfo.name.str()), - portInfo._hwPortInfo.type, ToMlirType(portInfo._origType, true))); + portInfo._hwPortInfo.type, ToMlirTypeAliased(portInfo._origType, true, *this))); } break; diff --git a/compiler/cpp/circt_util.h b/compiler/cpp/circt_util.h index 4ea6e65b..801a4ab4 100644 --- a/compiler/cpp/circt_util.h +++ b/compiler/cpp/circt_util.h @@ -88,6 +88,9 @@ circt::seq::ClockType GetClockType(); mlir::Type ToMlirType(const Type* typeIn, bool signedness = false); +class ModuleDeclarationHelper; +mlir::Type ToMlirTypeAliased(const Type* typeIn, bool signedness, ModuleDeclarationHelper& helper); + mlir::Value GetTypedZeros(circt::OpBuilder& opb, const mlir::Location& location, const mlir::Type& typeIn); mlir::Value PopCount(circt::OpBuilder& opb, const mlir::Location location, const mlir::ValueRange values, @@ -318,6 +321,10 @@ class ModuleDeclarationHelper void AddTypedefs(const std::string& typeScopeName); + void RegisterNamedType(const Type* kanagawaType); + + std::optional GetTypeAlias(const Type* kanagawaType) const; + mlir::Type GetInspectableTypeAlias(); mlir::ModuleOp MlirModule(); @@ -352,7 +359,7 @@ class ModuleDeclarationHelper const std::string& circtDesignName, const mlir::Type type); private: - mlir::Type GetTypeAlias(const std::string& name, const mlir::Type& referencedType); + mlir::Type CreateTypeAlias(const std::string& name, const mlir::Type& referencedType); private: void AssertStructsMatch(const mlir::Type& circtTypeAlias, const std::string& otherStructName); @@ -405,6 +412,13 @@ class ModuleDeclarationHelper circt::hw::TypeScopeOp _typeScopeOp; + // Maps Kanagawa Type* to hw::TypeAliasType for named types + std::map _typeAliasCache; + + // Maps type name to TypeAliasType to prevent duplicate TypedeclOps + // when distinct Type* pointers share the same name + std::map _typeAliasByName; + bool _finished; bool _exportVerilog; diff --git a/compiler/cpp/verilog.cpp b/compiler/cpp/verilog.cpp index 8a22ecda..510f78a4 100644 --- a/compiler/cpp/verilog.cpp +++ b/compiler/cpp/verilog.cpp @@ -6571,8 +6571,9 @@ class VerilogCompiler coreModule.AddPort(port._name, port._input ? circt::hw::ModulePort::Direction::Input : circt::hw::ModulePort::Direction::Output, - ToMlirType(port._type), port._type, port._portSemantics, port._channelSemantics, - port._channelName, port._fieldName); + ToMlirType(port._type), port._type, + port._portSemantics, port._channelSemantics, port._channelName, + port._fieldName); } } @@ -6632,7 +6633,8 @@ class VerilogCompiler { const Type *type = functionNode->GetParameterType(i); coreModule.AddPort(prefix + "_" + functionNode->GetParameterName(i) + "_out", - circt::hw::ModulePort::Direction::Output, ToMlirType(type), type, + circt::hw::ModulePort::Direction::Output, + ToMlirType(type), type, EsiPortSemantics::Payload, EsiChannelSemantics::FromGeneratedHw, EsiChannelName::Arguments, functionNode->GetParameterName(i)); } @@ -6656,8 +6658,9 @@ class VerilogCompiler { const Type *type = functionNode->GetReturnType(); coreModule.AddPort(prefix + "_result_in", circt::hw::ModulePort::Direction::Input, - ToMlirType(type), type, EsiPortSemantics::Payload, - EsiChannelSemantics::ToGeneratedHw, EsiChannelName::Results); + ToMlirType(type), type, + EsiPortSemantics::Payload, EsiChannelSemantics::ToGeneratedHw, + EsiChannelName::Results); } if (!isNoBackpressure) @@ -7113,6 +7116,19 @@ class VerilogCompiler // to ensure that the generate `ifndef _TYPESCOPE_* macros all agree coreModule.AddTypedefs("CoreModuleTypeScope"); + // Register named types as type aliases in the CIRCT IR type scope + // so that port types and ESI channel payloads use named type aliases + // _exportedTypes is already topologically sorted by SortExportedTypes() + for (const Type *const type : _program._exportedTypes) + { + coreModule.RegisterNamedType(type); + } + + for (const auto &t : _program._exportedTypedefs) + { + coreModule.RegisterNamedType(t.second); + } + // Input and outputs of the KanagawaCore module DeclareCorePorts(coreModule, resetReplicas); diff --git a/test/interface/circt/CMakeLists.txt b/test/interface/circt/CMakeLists.txt index 6d70536e..0d268a33 100644 --- a/test/interface/circt/CMakeLists.txt +++ b/test/interface/circt/CMakeLists.txt @@ -70,3 +70,15 @@ add_circt_mlir_test(esi_wrapper_ports TEST "python3 ${CMAKE_CURRENT_SOURCE_DIR}/check_esi_wrapper_ports.py ${CMAKE_CURRENT_BINARY_DIR}/esi_wrapper_ports" ) + +add_circt_mlir_test(named_type_aliases + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/named_type_aliases.k + OPTIONS + --backend=sv + --skip-circt-lowering + --base-library=${CMAKE_SOURCE_DIR}/library/base.k + --import-dir=${CMAKE_SOURCE_DIR}/library + --place-iterations=1 + TEST + "python3 ${CMAKE_CURRENT_SOURCE_DIR}/check_named_type_aliases.py ${CMAKE_CURRENT_BINARY_DIR}/named_type_aliases" +) diff --git a/test/interface/circt/check_named_type_aliases.py b/test/interface/circt/check_named_type_aliases.py new file mode 100644 index 00000000..07fb645f --- /dev/null +++ b/test/interface/circt/check_named_type_aliases.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import re +import sys +from pathlib import Path + + +def find_line(lines, marker): + matches = [line for line in lines if marker in line] + if len(matches) != 1: + raise RuntimeError(f"expected one {marker!r} line, found {len(matches)}") + return matches[0] + + +def require(line, marker): + if marker not in line: + raise RuntimeError(f"expected {marker!r} in:\n {line.strip()}") + + +def main(): + mlir_files = sorted(Path(sys.argv[1]).glob("*.mlir")) + if len(mlir_files) != 1: + raise RuntimeError(f"expected one MLIR file, found {len(mlir_files)}") + + lines = mlir_files[0].read_text().splitlines() + + state = find_line(lines, "hw.typedecl @E,") + metadata = find_line(lines, "hw.typedecl @S,") + payload = find_line(lines, "hw.typedecl @U") + body = find_line(lines, "hw.typedecl @Foo,") + packet = find_line(lines, "hw.typedecl @Bar,") + + declarations = [state, metadata, payload, body, packet] + if [lines.index(line) for line in declarations] != sorted(lines.index(line) for line in declarations): + raise RuntimeError("named types were not emitted in dependency order") + + require(state, ": ui2") + + require(metadata, "!hw.struct") + match = re.search(r"hw\.typedecl @([^,]+),", payload) + if match is None: + raise RuntimeError(f"could not parse template union alias from:\n {payload.strip()}") + payload_alias = f"!hw.typealias<@CoreModuleTypeScope::@{match.group(1)}," + + require(body, "!hw.union<") + require(body, f"u: {payload_alias}") + require(body, "s: !hw.typealias<@CoreModuleTypeScope::@S,") + require(body, "a: !hw.array<2xui4>") + require(body, "x: ui8") + + require(packet, "!hw.struct<") + require(packet, "a: !hw.array<2x") + require(packet, payload_alias) + require(packet, "f: !hw.typealias<@CoreModuleTypeScope::@Foo,") + require(packet, "s: !hw.typealias<@CoreModuleTypeScope::@S,") + + run = find_line(lines, 'kanagawa.port.input "Run" sym @Run') + require(run, "!hw.typealias<@CoreModuleTypeScope::@Bar,") + require(run, "!hw.struct +union U +{ + T x; + int y; +} + +using U8 = U; + +union Foo +{ + uint8 x; + uint4[2] a; + S s; + U8 u; +} + +struct Bar +{ + S s; + Foo f; + U8[2] a; +} + +inline auto Id(Bar x) +{ + auto y = x; + return y; +} + +using BarT = decltype(Id({})); + +class Test +{ +private: + (BarT, Foo, E)->BarT callback; + +public: + BarT Run(BarT x, Foo y, E z) + { + auto result = callback(x, y, z); + return Id(result); + } +} + +export Test; \ No newline at end of file