diff --git a/docs/GettingStarted/debug.md b/docs/GettingStarted/debug.md index df3c470d5e..157de12125 100644 --- a/docs/GettingStarted/debug.md +++ b/docs/GettingStarted/debug.md @@ -32,4 +32,7 @@ vast-opt --pass-pipeline="pipeline-string" ### Debug Pipeline -With the `-vast-debug` option, you get more detailed crash reports. It shows MLIR operations when there's an error and provides current stack traces. \ No newline at end of file +With the `-vast-debug` option, you get more detailed crash reports. It shows MLIR operations when there's an error and provides current stack traces. + +Sometimes it is needed to examine the results of conversion steps more closely to discover what went wrong. `-vast-snapshot-at=pass1;...;passN` will instrument conversion pipeline to store a result of `passN` into a file after it is applied. Name of the file will be in the form of: `basename.pass_name`. +Passing `"*"` in the string will result in output after every step. diff --git a/docs/Tools/vast-front.md b/docs/Tools/vast-front.md index 8bfff41e03..8c836a0b38 100644 --- a/docs/Tools/vast-front.md +++ b/docs/Tools/vast-front.md @@ -44,6 +44,10 @@ Additional customization options include: - `-vast-disable-vast-verifier` - Skips verification of the produced VAST MLIR module. +- `vast-snapshot-at="pass1;...;passN` + - After each pass that was specified as an option store MLIR into a file (format is `src.pass_name`). + - `"*"` stores snapshot after every conversion. + ## Pipelines -WIP pipelines documentation \ No newline at end of file +WIP pipelines documentation diff --git a/include/vast/Dialect/HighLevel/HighLevelOps.td b/include/vast/Dialect/HighLevel/HighLevelOps.td index 45382c5dec..cae0d91d3a 100644 --- a/include/vast/Dialect/HighLevel/HighLevelOps.td +++ b/include/vast/Dialect/HighLevel/HighLevelOps.td @@ -634,7 +634,10 @@ class CastOp< string mnemonic, list< Trait > traits = [] > let assemblyFormat = "$value $kind attr-dict `:` type($value) `->` type($result)"; } -def ImplicitCastOp : CastOp< "implicit_cast" >; +def ImplicitCastOp : CastOp< "implicit_cast" > { + let hasFolder = 1; +} + def CStyleCastOp : CastOp< "cstyle_cast" >; def BuiltinBitCastOp : CastOp< "builtin_bitcast" >; diff --git a/include/vast/Dialect/HighLevel/Passes.hpp b/include/vast/Dialect/HighLevel/Passes.hpp index 5b02b5e9c2..a912d393d9 100644 --- a/include/vast/Dialect/HighLevel/Passes.hpp +++ b/include/vast/Dialect/HighLevel/Passes.hpp @@ -30,6 +30,8 @@ namespace vast::hl { std::unique_ptr< mlir::Pass > createSpliceTrailingScopes(); + std::unique_ptr< mlir::Pass > createLowerEnumsPass(); + /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "vast/Dialect/HighLevel/Passes.h.inc" diff --git a/include/vast/Dialect/HighLevel/Passes.td b/include/vast/Dialect/HighLevel/Passes.td index 1e11850065..fb2dba43d2 100644 --- a/include/vast/Dialect/HighLevel/Passes.td +++ b/include/vast/Dialect/HighLevel/Passes.td @@ -120,4 +120,16 @@ def SpliceTrailingScopes : Pass<"vast-hl-splice-trailing-scopes", "mlir::ModuleO let constructor = "vast::hl::createSpliceTrailingScopes()"; } +def LowerEnums : Pass<"vast-hl-lower-enums", "mlir::ModuleOp"> { + let summary = "Lower high level representation of enums."; + let description = [{ + Lower enum values and types into their underlying representations. Definition of enum + types are erased once conversion is finished. + }]; + + let constructor = "vast::hl::createLowerEnumsPass()"; + let dependentDialects = [ + "vast::hl::HighLevelDialect" + ]; +} #endif // VAST_DIALECT_HIGHLEVEL_PASSES_TD diff --git a/include/vast/Frontend/Options.hpp b/include/vast/Frontend/Options.hpp index 6586f48c83..6fb5026181 100644 --- a/include/vast/Frontend/Options.hpp +++ b/include/vast/Frontend/Options.hpp @@ -84,6 +84,8 @@ namespace vast::cc constexpr option_t simplify = "simplify"; constexpr option_t canonicalize = "canonicalize"; + constexpr option_t snapshot_at = "snapshot-at"; + llvm::Twine disable(string_ref pipeline_name); constexpr option_t show_locs = "show-locs"; diff --git a/include/vast/Frontend/Pipelines.hpp b/include/vast/Frontend/Pipelines.hpp index 85e65c5881..9db2481a75 100644 --- a/include/vast/Frontend/Pipelines.hpp +++ b/include/vast/Frontend/Pipelines.hpp @@ -48,7 +48,8 @@ namespace vast::cc { std::unique_ptr< vast_pipeline > setup_pipeline( pipeline_source src, target_dialect trg, mcontext_t &mctx, - const vast_args &vargs + const vast_args &vargs, + std::string snapshot_prefix ); } // namespace vast::cc diff --git a/include/vast/Util/Pipeline.hpp b/include/vast/Util/Pipeline.hpp index 39134f9707..537d219a07 100644 --- a/include/vast/Util/Pipeline.hpp +++ b/include/vast/Util/Pipeline.hpp @@ -7,6 +7,7 @@ VAST_RELAX_WARNINGS #include #include +#include #include #include #include @@ -65,9 +66,77 @@ namespace vast { virtual void schedule(pipeline_step_ptr step) = 0; + void print_on_error(llvm::raw_ostream &os) { + enableIRPrinting( + [](auto *, auto *) { return false; }, // before + [](auto *, auto *) { return true; }, // after + false, // module scope + false, // after change + true, // after failure + os + ); + } + llvm::DenseSet< pass_id_t > seen; }; + struct with_snapshots : mlir::PassInstrumentation + { + using output_stream_ptr = std::shared_ptr< llvm::raw_pwrite_stream >; + using passes_t = std::vector< llvm::StringRef >; + + std::string file_prefix; + + with_snapshots(llvm::StringRef file_prefix) + : file_prefix(file_prefix.str()) + {} + + // We return `shared_ptr` in case we may want to keep the stream open for longer + // in some derived class. Should not make a difference, snapshoting will be expensive + // anyway. + virtual output_stream_ptr make_output_stream(mlir::Pass *pass) { + std::string name = file_prefix + "." + pass->getArgument().str(); + std::error_code ec; + auto os = std::make_shared< llvm::raw_fd_ostream >(name, ec); + VAST_CHECK(!ec, "Cannot open file to store snapshot at, ec: {0}", ec.message()); + return std::move(os); + } + + virtual bool should_snapshot(mlir::Pass *pass) const = 0; + + void runAfterPass(mlir::Pass *pass, operation op) override { + if (!should_snapshot(pass)) + return; + + auto os = make_output_stream(pass); + (*os) << *op; + } + }; + + struct snapshot_at_passes : with_snapshots { + using base = with_snapshots; + + passes_t snapshot_at; + + template< typename ... Args > + snapshot_at_passes(const passes_t &snapshot_at, Args && ... args) + : base(std::forward< Args >(args)...), snapshot_at(snapshot_at) + {} + + virtual bool should_snapshot(mlir::Pass *pass) const override { + return std::ranges::count(snapshot_at, pass->getArgument()); + } + }; + + struct snapshot_all : with_snapshots { + using base = with_snapshots; + using base::base; + + bool should_snapshot(mlir::Pass *) const override { + return true; + } + }; + using pipeline_step_builder = std::function< pipeline_step_ptr(void) >; diff --git a/lib/vast/Conversion/FromHL/EmitLazyRegions.cpp b/lib/vast/Conversion/FromHL/EmitLazyRegions.cpp index 87ed5e6fa7..a3bc6db016 100644 --- a/lib/vast/Conversion/FromHL/EmitLazyRegions.cpp +++ b/lib/vast/Conversion/FromHL/EmitLazyRegions.cpp @@ -133,6 +133,7 @@ namespace vast static conversion_target create_conversion_target(mcontext_t &context) { conversion_target target(context); target.addLegalDialect< vast::core::CoreDialect >(); + target.markUnknownOpDynamicallyLegal([](auto){ return true; }); return target; } diff --git a/lib/vast/Conversion/FromHL/ToLLGEPs.cpp b/lib/vast/Conversion/FromHL/ToLLGEPs.cpp index fcace3f85e..60d1aed005 100644 --- a/lib/vast/Conversion/FromHL/ToLLGEPs.cpp +++ b/lib/vast/Conversion/FromHL/ToLLGEPs.cpp @@ -29,7 +29,12 @@ namespace vast { logical_result matchAndRewrite( op_t op, typename op_t::Adaptor ops, conversion_rewriter &rewriter ) const override { - auto parent_type = ops.getRecord().getType(); + auto parent_type = [&] { + auto type = ops.getRecord().getType(); + if (auto ptr = mlir::dyn_cast< hl::PointerType >(type)) + return ptr.getElementType(); + return type; + }(); auto mod = op->getParentOfType< vast_module >(); if (!mod) { diff --git a/lib/vast/Dialect/HighLevel/HighLevelOps.cpp b/lib/vast/Dialect/HighLevel/HighLevelOps.cpp index 5f606c9acf..6035693a2d 100644 --- a/lib/vast/Dialect/HighLevel/HighLevelOps.cpp +++ b/lib/vast/Dialect/HighLevel/HighLevelOps.cpp @@ -61,6 +61,22 @@ namespace vast::hl return {}; } + namespace { + + FoldResult fold_integral_cast(auto &self, auto adaptor) { + if (self.getResult().getType() == self.getValue().getType()) + return self.getValue(); + return {}; + } + + } // namespace + + FoldResult ImplicitCastOp::fold(FoldAdaptor adaptor) { + if (getKind() == CastKind::IntegralCast) + return fold_integral_cast(*this, adaptor); + return {}; + } + FoldResult AddIOp::fold(FoldAdaptor adaptor) { return checked_int_arithmetic(getType(), adaptor, [] (const ap_sint &lhs, const ap_sint &rhs) -> std::optional< ap_sint > { diff --git a/lib/vast/Dialect/HighLevel/Passes.cpp b/lib/vast/Dialect/HighLevel/Passes.cpp index e7025f9c8d..56b958f4f1 100644 --- a/lib/vast/Dialect/HighLevel/Passes.cpp +++ b/lib/vast/Dialect/HighLevel/Passes.cpp @@ -47,6 +47,10 @@ namespace vast::hl::pipeline { return pass(hl::createUDEPass).depends_on(splice_trailing_scopes); } + static pipeline_step_ptr lower_enums() { + return pass(hl::createLowerEnumsPass).depends_on(desugar); + } + pipeline_step_ptr simplify() { return compose("simplify", conv::pipeline::to_hlbi, @@ -60,7 +64,7 @@ namespace vast::hl::pipeline { // stdtypes passes // pipeline_step_ptr stdtypes() { - return pass(hl::createHLLowerTypesPass).depends_on(desugar); + return pass(hl::createHLLowerTypesPass).depends_on(desugar, lower_enums); } } // namespace vast::hl::pipeline diff --git a/lib/vast/Dialect/HighLevel/Transforms/CMakeLists.txt b/lib/vast/Dialect/HighLevel/Transforms/CMakeLists.txt index 66fc8da163..e363f16c24 100644 --- a/lib/vast/Dialect/HighLevel/Transforms/CMakeLists.txt +++ b/lib/vast/Dialect/HighLevel/Transforms/CMakeLists.txt @@ -5,6 +5,7 @@ add_vast_conversion_library(HighLevelTransforms HLLowerTypes.cpp DCE.cpp LowerElaboratedTypes.cpp + LowerEnums.cpp LowerTypeDefs.cpp SpliceTrailingScopes.cpp UDE.cpp diff --git a/lib/vast/Dialect/HighLevel/Transforms/LowerEnums.cpp b/lib/vast/Dialect/HighLevel/Transforms/LowerEnums.cpp new file mode 100644 index 0000000000..4e900b8e2d --- /dev/null +++ b/lib/vast/Dialect/HighLevel/Transforms/LowerEnums.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2021-present, Trail of Bits, Inc. + +#include "vast/Dialect/HighLevel/Passes.hpp" + +VAST_RELAX_WARNINGS +#include +#include +#include +#include + +#include +#include +VAST_UNRELAX_WARNINGS + +#include "PassesDetails.hpp" + +#include "vast/Dialect/HighLevel/HighLevelOps.hpp" +#include "vast/Dialect/HighLevel/HighLevelUtils.hpp" +#include "vast/Dialect/LowLevel/LowLevelOps.hpp" + +#include "vast/Conversion/TypeConverters/TypeConvertingPattern.hpp" + +#include "vast/Util/Common.hpp" +#include "vast/Util/DialectConversion.hpp" +#include "vast/Util/Symbols.hpp" +#include "vast/Util/Terminator.hpp" + +namespace vast::hl { + + struct enums_info { + using const_decl = hl::EnumConstantOp; + using enum_decl = hl::EnumDeclOp; + + llvm::DenseMap< llvm::StringRef, const_decl > id_to_value; + llvm::DenseMap< llvm::StringRef, enum_decl > name_to_decl; + + enums_info(vast_module mod) { + collect_enum_values(mod); + } + + void collect_enum_values(vast_module root) { + auto walker = [&](operation op) { + if (auto decl = mlir::dyn_cast< const_decl >(op)) { + VAST_ASSERT(!id_to_value.count(decl.getName())); + id_to_value[decl.getName()] = decl; + } else if (auto decl = mlir::dyn_cast< enum_decl >(op)) { + name_to_decl[decl.getName()] = decl; + } + }; + + root->walk(walker); + } + }; + + struct enum_type_converter + : conv::tc::identity_type_converter + , conv::tc::mixins< enum_type_converter > + { + mcontext_t &mctx; + const enums_info &info; + + enum_type_converter(mcontext_t &mctx, const enums_info &info) + : mctx(mctx), info(info) + { + init(); + } + + auto convert_enum() { + return [&](hl::RecordType record) -> maybe_type_t { + // If not in the mapping it is not an `enum` (both `union` and `struct` use + // `record` as well. + auto name = record.getName(); + auto it = info.name_to_decl.find(name); + if (it == info.name_to_decl.end()) + return {}; + + // Need to be a separate decl to avoid compiler thinking it is a `const`. + hl::EnumDeclOp decl = it->second; + auto type = decl.getType(); + VAST_CHECK(type, "enum did not provide `getType()`, {0}", decl); + return type; + }; + } + + void init() { + addConversion(convert_enum()); + } + }; + + struct type_rewriter : pattern_rewriter { + type_rewriter(mcontext_t *mctx) : pattern_rewriter(mctx) {} + }; + + using type_conversion_pattern = conv::tc::generic_type_converting_pattern< enum_type_converter >; + + struct LowerEnumsPass : LowerEnumsBase< LowerEnumsPass > { + void runOnOperation() override { + auto enums = enums_info(this->getOperation()); + + // Investigate if we want these as patterns instead. I would be curious + // to see what's the runtime difference of repeating the traversal vs the + // bookkeeping the conversion needs to work. + // For now I am keeping this separate in case some random edge cases needs to be + // added (this is easier to debug), + // but eventually we probably should swap these to patterns. + replace_constants(enums); + replace_types(enums); + + erase_enums(enums); + + // TODO: Now the IR can be in a state where there are casts of `T -> T`. Should + // we simply call canonizer? + } + + void replace_constants(const enums_info &enums) { + auto replace_constants = [&](hl::EnumRefOp ref) { + auto name = ref.getValue(); + auto it = enums.id_to_value.find(name); + VAST_ASSERT(it != enums.id_to_value.end()); + + auto bld = mlir::OpBuilder(ref); + hl::EnumConstantOp enum_constant = it->second; + auto value = enum_constant.getValue(); + + // Make the integer + operation i = bld.create< hl::ConstantOp >(ref.getLoc(), ref.getType(), value); + ref.replaceAllUsesWith(i); + ref->erase(); + }; + + // First let's replace all constants + this->getOperation()->walk(replace_constants); + } + + void replace_types(const enums_info &enums) { + auto tc = enum_type_converter(getContext(), enums); + auto pattern = type_conversion_pattern(tc, getContext()); + auto replace_types = [&](operation root) { + type_rewriter bld(&getContext()); + // We don't really care, failure only means that pattern was not applied, + // which is a valid result. + [[maybe_unused]] auto status = pattern.replace(root, bld); + }; + this->getOperation()->walk(replace_types); + } + + void erase_enums(const enums_info &enums) { + for (auto &[_, decl] : enums.name_to_decl) + decl->erase(); + } + }; +} // namespace vast::hl + +std::unique_ptr< mlir::Pass > vast::hl::createLowerEnumsPass() { + return std::make_unique< vast::hl::LowerEnumsPass >(); +} diff --git a/lib/vast/Frontend/Consumer.cpp b/lib/vast/Frontend/Consumer.cpp index c7c6e73c73..bba2278e7e 100644 --- a/lib/vast/Frontend/Consumer.cpp +++ b/lib/vast/Frontend/Consumer.cpp @@ -14,6 +14,8 @@ VAST_RELAX_WARNINGS #include VAST_UNRELAX_WARNINGS +#include + #include "vast/CodeGen/CodeGenContext.hpp" #include "vast/CodeGen/CodeGenDriver.hpp" @@ -205,7 +207,11 @@ namespace vast::cc { } // Setup and execute vast pipeline - auto pipeline = setup_pipeline(pipeline_source::ast, target, *mctx, vargs); + auto file_entry = src_mgr.getFileEntryRefForID(main_file_id); + VAST_CHECK(file_entry, "failed to recover file entry ref"); + auto snapshot_prefix = std::filesystem::path(file_entry->getName().str()).stem(); + + auto pipeline = setup_pipeline(pipeline_source::ast, target, *mctx, vargs, snapshot_prefix); VAST_CHECK(pipeline, "failed to setup pipeline"); auto result = pipeline->run(mod); diff --git a/lib/vast/Frontend/Options.cpp b/lib/vast/Frontend/Options.cpp index 3d953e7b93..7d212c1169 100644 --- a/lib/vast/Frontend/Options.cpp +++ b/lib/vast/Frontend/Options.cpp @@ -49,7 +49,6 @@ namespace vast::cc { std::optional< string_ref > vast_args::get_option(string_ref name) const { if (auto opt = detail::get_option_impl(args, name)) { if (auto [lhs, rhs] = opt->split('='); !rhs.empty()) { - VAST_ASSERT(!is_options_list(rhs)); return rhs; } } diff --git a/lib/vast/Frontend/Pipelines.cpp b/lib/vast/Frontend/Pipelines.cpp index 69fd4db475..1008fb5970 100644 --- a/lib/vast/Frontend/Pipelines.cpp +++ b/lib/vast/Frontend/Pipelines.cpp @@ -125,18 +125,20 @@ namespace vast::cc { pipeline_source src, target_dialect trg, mcontext_t &mctx, - const vast_args &vargs + const vast_args &vargs, + std::string snapshot_prefix ) { auto passes = std::make_unique< vast_pipeline >(mctx, vargs); - - passes->enableIRPrinting( - [](auto *, auto *) { return false; }, // before - [](auto *, auto *) { return true; }, // after - false, // module scope - false, // after change - true, // after failure - llvm::errs() - ); + passes->print_on_error(llvm::errs()); + + if (auto snapshot_at = vargs.get_options_list(opt::snapshot_at)) { + auto instrument = [&]() -> std::unique_ptr< with_snapshots > { + if (std::ranges::count(*snapshot_at, llvm::StringRef("*"))) + return std::make_unique< snapshot_all >(snapshot_prefix); + return std::make_unique< snapshot_at_passes >(*snapshot_at, snapshot_prefix); + }(); + passes->addInstrumentation(std::move(instrument)); + } // generate high level MLIR in case of AST input if (pipeline_source::ast == src) { diff --git a/test/vast/Conversion/enum-a.c b/test/vast/Conversion/enum-a.c new file mode 100644 index 0000000000..85f788e250 --- /dev/null +++ b/test/vast/Conversion/enum-a.c @@ -0,0 +1,36 @@ +// RUN: %vast-front -vast-snapshot-at="vast-hl-lower-enums" %s +// RUN: %file-check %s -input-file=$(basename %s .c).vast-hl-lower-enums -check-prefix=ENUM + +enum E : int { + E_a = 0, + E_b = 1 +}; + +// ENUM: module{{.*}} +// ENUM-NEXT: hl.func{{.*}} +int main() { + + // ENUM: {{.*}} = hl.var "a" : !hl.lvalue = { + // ENUM: {{.*}} = hl.const #core.integer<0> : !hl.int + // ENUM: hl.value.yield {{.*}} : !hl.int + // ENUM: } + int a = E_a; + + // ENUM: {{.*}} = hl.var "b" : !hl.lvalue = { + // ENUM: {{.*}} = hl.const #core.integer<0> : !hl.int + // ENUM: hl.value.yield {{.*}} : !hl.int + // ENUM: } + enum E b = E_a; + + // ENUM: {{.*}} = hl.var "c" : !hl.lvalue = { + // ENUM: {{.*}} = hl.const #core.integer<0> : !hl.int + // ENUM: hl.value.yield {{.*}} : !hl.int + // ENUM: } + enum E c = 0; + + // ENUM: {{.*}} = hl.var "d" : !hl.lvalue = { + // ENUM: {{.*}} = hl.ref {{.*}} : (!hl.lvalue) -> !hl.lvalue + // ENUM: hl.value.yield {{.*}} : !hl.int + // ENUM: } + enum E d = a; +} diff --git a/test/vast/Conversion/enum-b.c b/test/vast/Conversion/enum-b.c new file mode 100644 index 0000000000..ef4c268a0f --- /dev/null +++ b/test/vast/Conversion/enum-b.c @@ -0,0 +1,41 @@ +// RUN: %vast-front -c -vast-snapshot-at="vast-hl-lower-enums" %s +// RUN: %file-check %s -input-file=$(basename %s .c).vast-hl-lower-enums -check-prefix=ENUM + +enum E : char { + E_a = 0, + E_b = E_a + 1 +}; + +// ENUM: hl.func @id {{.*}} ({{.*}}!hl.lvalue) -> !hl.char { +enum E id(enum E e) { + return e; +} + +int main() { + // ENUM: {{.*}} = hl.var "a" : !hl.lvalue = { + // ENUM: [[A7:%[0-9]+]] = hl.call @id({{.*}}) : (!hl.char) -> !hl.char + // ENUM: hl.value.yield [[A7]] : !hl.char + // ENUM: } + enum E a = id(E_b); + // ENUM: {{.*}} = hl.var "b" : !hl.lvalue = { + // ENUM: [[B6:%[0-9]+]] = hl.implicit_cast {{.*}} IntegralCast : !hl.int -> !hl.char + // ENUM: [[B7:%[0-9]+]] = hl.call @id([[B6]]) : (!hl.char) -> !hl.char + // ENUM: hl.value.yield [[B7]] : !hl.char + // ENUM: } + enum E b = id(0); + + // ENUM: {{.*}} = hl.var "c" : !hl.lvalue = { + // ENUM: [[C7:%[0-9]+]] = hl.call @id({{.*}}) : (!hl.char) -> !hl.char + // ENUM: [[C8:%[0-9]+]] = hl.implicit_cast [[C7]] IntegralCast : !hl.char -> !hl.int + // ENUM: hl.value.yield [[C8]] : !hl.int + // ENUM: } + int c = id(E_b); + + // ENUM: {{.*}} = hl.var "d" : !hl.lvalue = { + // ENUM: [[D6:%[0-9]+]] = hl.implicit_cast {{.*}} IntegralCast : !hl.int -> !hl.char + // ENUM: [[D7:%[0-9]+]] = hl.call @id([[D6]]) : (!hl.char) -> !hl.char + // ENUM: [[D8:%[0-9]+]] = hl.implicit_cast [[D7]] IntegralCast : !hl.char -> !hl.int + // ENUM: hl.value.yield [[D8]] : !hl.int + // ENUM: } + int d = id(0); +} diff --git a/test/vast/Conversion/enum-global.c b/test/vast/Conversion/enum-global.c new file mode 100644 index 0000000000..14aa8cceb2 --- /dev/null +++ b/test/vast/Conversion/enum-global.c @@ -0,0 +1,29 @@ +// RUN: %vast-front -c -vast-snapshot-at="vast-hl-lower-enums;vast-irs-to-llvm" %s +// RUN: %file-check %s -input-file=$(basename %s .c).vast-hl-lower-enums -check-prefix=ENUM +// RUN: %file-check %s -input-file=$(basename %s .c).vast-irs-to-llvm -check-prefix=LLVM + +enum E : char { + E_a = 0 +}; + +// ENUM: {{.*}} = hl.var "a" : !hl.lvalue = { +// ENUM: [[A2:%[0-9]+]] = hl.const #core.integer<0> : !hl.int +// ENUM: [[A3:%[0-9]+]] = hl.implicit_cast [[A2]] IntegralCast : !hl.int -> !hl.char +// ENUM: hl.value.yield [[A3]] : !hl.char +// ENUM: } + +// LLVM: llvm.mlir.global internal constant @a() {{.*}} : i8 { +// LLVM: [[A1:%[0-9]+]] = llvm.trunc {{.*}} : i32 to i8 +// LLVM: llvm.return [[A1]] : i8 +// LLVM: } +enum E a = 0; + +typedef enum E(*fn_ptr)(enum E); + +// ENUM: {{.*}} = hl.var "p" : !hl.lvalue) -> (!hl.char)>>>> = { + +// LLVM: llvm.mlir.global internal constant @p() {{.*}} : !llvm.ptr { +// LLVM: [[P1:%[0-9]+]] = llvm.mlir.zero : !llvm.ptr +// LLVM: llvm.return [[P1]] : !llvm.ptr +// LLVM: } +fn_ptr p = 0;