diff --git a/CMakeLists.txt b/CMakeLists.txt index db2602de..da16e26a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -492,6 +492,12 @@ endif() add_compile_options(-Wall -Wextra) +# LLVM/MLIR is prebuilt with RTTI and exceptions disabled (see +# extern/llvm-project/bin/llvm-config --cxxflags). Compile project code with +# the same settings so the CoIR tools link against the prebuilt bitcode +# libraries without typeinfo/exception symbol mismatches. +add_compile_options(-fno-rtti -fno-exceptions) + if(EMSCRIPTEN) # Choreo is developed against GCC; suppress Clang-specific pedantic warnings # that do not indicate actual bugs when cross-compiling to WASM. @@ -544,7 +550,9 @@ include(${CMAKE_SOURCE_DIR}/cmake/FileCheckBootstrap.cmake) if(NOT EMSCRIPTEN) set(EXTERN_LIBS - stdc++fs + # stdc++fs was previously required for GCC/libstdc++'s std::filesystem. + # With the clang++/libc++ toolchain, std::filesystem is provided by libc++ + # itself, and libstdc++fs is neither available nor ABI-compatible. ) if(ENABLE_CUDA) @@ -572,6 +580,10 @@ if(CHOREO_BUILD_CHOREO) add_dependencies(choreo parser_deps) add_dependencies(copp parser_deps) + # choreo_main.cpp drives the bison-generated parser directly (Scanner/Parser), + # which requires RTTI + exceptions; the rest of the target stays -fno-rtti. + target_compile_options(choreo PRIVATE -frtti -fexceptions) + target_link_libraries(choreo PRIVATE parse core codegen support pp -Wl,--whole-archive targets -Wl,--no-whole-archive diff --git a/Makefile b/Makefile index eeb2d03b..f6359fad 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,10 @@ SHELL:=/bin/bash WORK_DIR:=$(CURDIR) TOOLCHAIN_DIR=$(WORK_DIR)/extern +LLVM_BIN_DIR=$(TOOLCHAIN_DIR)/llvm-project/bin +LLVM_CC=$(LLVM_BIN_DIR)/clang +LLVM_CXX=$(LLVM_BIN_DIR)/clang++ +LLVM_LIBCXX_DIR=$(TOOLCHAIN_DIR)/llvm-project/lib/x86_64-unknown-linux-gnu TOOLS_DIR=$(WORK_DIR)/tools SCRIPT_DIR=$(WORK_DIR)/scripts RT_DIR=$(WORK_DIR)/runtime @@ -107,9 +111,13 @@ symlink-coir: define build-croqtile @echo "=== Building CoIR tools ($(1)) ===" - $(CMAKE) -S $(WORK_DIR) -B $(2) \ + PATH=$(LLVM_BIN_DIR):$$PATH $(CMAKE) -S $(WORK_DIR) -B $(2) \ -G Ninja \ -DCMAKE_BUILD_TYPE=$(1) \ + -DCMAKE_C_COMPILER=$(LLVM_CC) \ + -DCMAKE_CXX_COMPILER=$(LLVM_CXX) \ + '-DCMAKE_CXX_FLAGS=-stdlib=libc++' \ + '-DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ -fuse-ld=lld -Wl,-rpath,$(LLVM_LIBCXX_DIR)' \ -DCHOREO_DEFAULT_TARGET=$(CHOREO_DEFAULT_TARGET) \ '-DCROQ_PROJECT=$(CROQ_PROJECT)' \ '-DCROQ_TARGET=$(CROQ_TARGET)' @@ -153,9 +161,13 @@ endef # ---- coir-only (no CoIR) ---- define build-coir-only @echo "=== Building CoIR tools ($(1)) ===" - $(CMAKE) -S $(WORK_DIR) -B $(2) \ + PATH=$(LLVM_BIN_DIR):$$PATH $(CMAKE) -S $(WORK_DIR) -B $(2) \ -G Ninja \ -DCMAKE_BUILD_TYPE=$(1) \ + -DCMAKE_C_COMPILER=$(LLVM_CC) \ + -DCMAKE_CXX_COMPILER=$(LLVM_CXX) \ + '-DCMAKE_CXX_FLAGS=-stdlib=libc++' \ + '-DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ -fuse-ld=lld -Wl,-rpath,$(LLVM_LIBCXX_DIR)' \ -DCHOREO_DEFAULT_TARGET=$(CHOREO_DEFAULT_TARGET) \ '-DCROQ_PROJECT=coir' \ '-DCROQ_TARGET=$(CROQ_TARGET)' @@ -263,8 +275,12 @@ CROQ_TARGET ?= all build-with-cmake-ninja: @echo "Starting build with CMake..." @if [ ! -d $(CMAKE_BUILD_DIR) ]; then mkdir -p $(CMAKE_BUILD_DIR); fi - $(CMAKE) -S . -B $(CMAKE_BUILD_DIR) -G Ninja \ + PATH=$(LLVM_BIN_DIR):$$PATH $(CMAKE) -S . -B $(CMAKE_BUILD_DIR) -G Ninja \ -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ + -DCMAKE_C_COMPILER=$(LLVM_CC) \ + -DCMAKE_CXX_COMPILER=$(LLVM_CXX) \ + '-DCMAKE_CXX_FLAGS=-stdlib=libc++' \ + '-DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++ -fuse-ld=lld -Wl,-rpath,$(LLVM_LIBCXX_DIR)' \ -DPUBLIC_PACKAGE=$(PUBLIC_PACKAGE) \ -DSTANDALONE=$(STANDALONE) \ -DCHOREO_DEFAULT_TARGET=$(CHOREO_DEFAULT_TARGET) \ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b0e3d130..c5de80ae 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -57,7 +57,8 @@ add_custom_target( set(PARSER_SOURCES ${CMAKE_BINARY_DIR}/parser.tab.cc ${CMAKE_BINARY_DIR}/scanner.yy.cc - # choreo_api.cpp hosts CompilerAPI::Parse, which drives the bison parser. + # choreo_api.cpp hosts CompilerAPI::Parse, which drives the bison parser and + # therefore needs RTTI + exceptions (compiled as part of the parse library). choreo_api.cpp ) @@ -106,6 +107,12 @@ set(PP_SOURCES # Add the libraries add_library(parse OBJECT ${PARSER_SOURCES}) +# Bison's variant-based semantic values (api.value.type variant) use typeid for +# runtime type checking, and its error recovery uses try/catch, so the generated +# parser needs RTTI and exceptions even though the rest of the project compiles +# with -fno-rtti -fno-exceptions. This only affects the auto-generated +# parser.tab.cc / scanner.yy.cc translation units. +target_compile_options(parse PRIVATE -frtti -fexceptions) add_library(core OBJECT ${CORE_SOURCES}) add_library(codegen OBJECT ${CODEGEN_SOURCES}) add_library(support OBJECT ${CL_SOURCES}) diff --git a/lib/Target/GPU/cute_target.cpp b/lib/Target/GPU/cute_target.cpp index 7236d557..f15b87de 100644 --- a/lib/Target/GPU/cute_target.cpp +++ b/lib/Target/GPU/cute_target.cpp @@ -47,6 +47,25 @@ ArchId NVGPUTarget::ResolveNativeArch() const { return ""; } +FenceSelection NVGPUTarget::SelectDMAFences(const ArchId& arch, Storage src, + Storage dst) const { + (void)arch; // B1 table is arch-invariant; gate on sm_90+ only if regressions. + FenceSelection sel; + if (src == Storage::GLOBAL && dst == Storage::SHARED) { + // Producer: threads release global before the DMA engine reads it. + sel.producer.push_back( + FenceKind{Storage::GLOBAL, FenceEntity::THREADS, FenceOrder::RELEASE}); + // Consumer: threads acquire shared after the DMA engine writes it. + sel.consumer.push_back( + FenceKind{Storage::SHARED, FenceEntity::THREADS, FenceOrder::ACQUIRE}); + } else if (src == Storage::SHARED && dst == Storage::GLOBAL) { + // S2G store: threads release shared before the engine reads it. + sel.producer.push_back( + FenceKind{Storage::SHARED, FenceEntity::THREADS, FenceOrder::RELEASE}); + } + return sel; +} + namespace { class CuteTarget : public NVGPUTarget { diff --git a/lib/Target/GPU/gpu_target.hpp b/lib/Target/GPU/gpu_target.hpp index f94b65cd..23e5a0a2 100644 --- a/lib/Target/GPU/gpu_target.hpp +++ b/lib/Target/GPU/gpu_target.hpp @@ -205,6 +205,13 @@ class NVGPUTarget : public GPUTarget { // default fence order is seq_cst and `sync.fence.seq_cst` is accepted. bool SupportsSeqCstFence(const ArchId&) const override { return true; } + // Fence requirements for DMA edges (B1 conservative table, storage-direction + // keyed). A global->shared edge releases global at the producer and acquires + // shared at the consumer; a shared->global edge releases shared at the + // producer. See the MARA fence-placement plan (Phase B1). + FenceSelection SelectDMAFences(const ArchId& arch, Storage src, + Storage dst) const override; + ArchId ResolveNativeArch() const override; }; diff --git a/lib/command_line.cpp b/lib/command_line.cpp index 89c4a189..6a8890dd 100644 --- a/lib/command_line.cpp +++ b/lib/command_line.cpp @@ -112,9 +112,10 @@ Option "Utilize native bf16 type when target platform support."); Option insert_dma_fences( - OptionKind::User, "--insert-dma-fences", "", true, + OptionKind::User, "--insert-dma-fences", "", false, "Insert memory fences between DMA producer and consumer sites " - "(default: true). Disable with --insert-dma-fences=false."); + "(default: false; experimental, not yet minimal). Enable with " + "--insert-dma-fences=true."); Option dump_fence_insertion( OptionKind::User, "--dump-fence-insertion", "", false, diff --git a/lib/fence_insertion.cpp b/lib/fence_insertion.cpp index daa07bd5..81345812 100644 --- a/lib/fence_insertion.cpp +++ b/lib/fence_insertion.cpp @@ -92,8 +92,11 @@ bool FenceInsertion::Visit(AST::DMA& n) { const BufferAccessEvent* dst_ev = nullptr; if (auto it = dma_dst_.find(&n); it != dma_dst_.end()) dst_ev = it->second; - const Storage src_storage = src_ev->storage; - const Storage dst_storage = dst_ev ? dst_ev->storage : Storage::NONE; + // Unannotated buffers default to global storage; normalize so the target's + // storage-directional table sees canonical GLOBAL (mirrors dma_plan.cpp). + const Storage src_storage = ProjectStorage(src_ev->storage); + const Storage dst_storage = + dst_ev ? ProjectStorage(dst_ev->storage) : Storage::NONE; FenceSelection sel = CCtx().GetTarget().SelectDMAFences( CCtx().GetArch(), src_storage, dst_storage); diff --git a/lib/pipeline.cpp b/lib/pipeline.cpp index 857ab75b..368b97bb 100644 --- a/lib/pipeline.cpp +++ b/lib/pipeline.cpp @@ -315,10 +315,14 @@ ASTPipeline& ASTPipeline::PlanSemanticRoutine() { // so the checker can validate events with thread count info) AddStage(); - // record the ordered (READ | WRITE) buffer access log consumed by - // FenceInsertion during codegen. + // record the ordered (READ | WRITE) buffer access log. AddStage(); + // compute and annotate DMA producer/consumer fences from the buffer access + // log. Runs in the semantic routine (not codegen) so that co2ir's + // RunFrontend path (NoCodegen=true) still gets the fence annotations. + AddStage(); + // apply the semantic check AddStage(); return *this; @@ -330,10 +334,6 @@ ASTPipeline& ASTPipeline::PlanCodeGenRoutine() { AddStage(); - // compute and annotate DMA producer/consumer fences from the buffer access - // log recorded during the semantic routine. - AddStage(); - // delegate to target for codegen plan if (!CCtx().GetTarget().PlanCodeGenStages(*this)) { errs() << "Failed to initialize codegen for target '" << CCtx().TargetName() diff --git a/tests/cli/insert_dma_fences.co b/tests/cli/insert_dma_fences.co index ac48b031..6f4998db 100644 --- a/tests/cli/insert_dma_fences.co +++ b/tests/cli/insert_dma_fences.co @@ -14,5 +14,5 @@ __co__ void foo() { } // HELP: --insert-dma-fences -// HELP: default: true +// HELP: default: false // BADVAL: Invalid value for boolean option '--insert-dma-fences' diff --git a/tools/coir/CMakeLists.txt b/tools/coir/CMakeLists.txt index b8bd8789..95c107c8 100644 --- a/tools/coir/CMakeLists.txt +++ b/tools/coir/CMakeLists.txt @@ -246,7 +246,7 @@ target_link_libraries(co2ir PRIVATE -Wl,--whole-archive targets -Wl,--no-whole-archive MLIRCoIRDialect MLIRCoIRTransforms MLIRCoIRDriver ${COIR_MLIR_LIBS} - stdc++fs Threads::Threads + Threads::Threads ) # Link codegen libs for emission support foreach(_lib ${COIR_CODEGEN_LIBS}) @@ -283,7 +283,7 @@ target_link_libraries(cocc PRIVATE -Wl,--whole-archive targets -Wl,--no-whole-archive MLIRCoIRDialect MLIRCoIRTransforms MLIRCoIRDriver ${COIR_MLIR_LIBS} - stdc++fs Threads::Threads + Threads::Threads ) # Link all codegen libs (whole-archive for static registrations) foreach(_lib ${COIR_CODEGEN_LIBS}) diff --git a/tools/coir/lib/ASTIRGen/ASTCoIRGen.cpp b/tools/coir/lib/ASTIRGen/ASTCoIRGen.cpp index 495161ed..62c02145 100644 --- a/tools/coir/lib/ASTIRGen/ASTCoIRGen.cpp +++ b/tools/coir/lib/ASTIRGen/ASTCoIRGen.cpp @@ -4337,17 +4337,22 @@ void ASTCoIRGen::emitFenceKinds(llvm::StringRef joined, mlir::Location loc) { if (kinds.empty()) return; // Emit one coir.fence per four-fold kind, carrying every axis - // (space, entity, order, scope) explicitly. Engine/proxy fences keep the - // auto scope (NONE), which codegen derives from `space`. + // (space, entity, order, scope) explicitly. An AUTO scope (NONE) resolves to + // the space's natural coherence level (GLOBAL -> DEVICE, SHARED -> GROUP, + // ...), mirroring the explicit sync.fence path in the parser. The backend + // (EmitCUDA/EmitHIP) requires a concrete scope, so resolve before lowering. for (const auto& kind : kinds) { if (kind.IsNone()) continue; + ParallelLevel scope = kind.scope; + if (scope == ParallelLevel::NONE) + scope = DefaultLevelForStorage(kind.space); builder.create( loc, coir::TensorMemorySpaceAttr::get(&IRContext(), lowerFenceSpace(kind.space)), coir::FenceEntityAttr::get(&IRContext(), lowerFenceEntity(kind.entity)), coir::FenceOrderAttr::get(&IRContext(), lowerFenceOrder(kind.order)), - LowerParallelLevel(kind.scope)); + LowerParallelLevel(scope)); } } diff --git a/tools/coir/tests/irgen/dma-fence.co b/tools/coir/tests/irgen/dma-fence.co new file mode 100644 index 00000000..0b3e19e3 --- /dev/null +++ b/tools/coir/tests/irgen/dma-fence.co @@ -0,0 +1,40 @@ +// RUN: co2ir --insert-dma-fences=true %s | FileCheck %s + +// B1: DMA edges emit coir.fence ops via the NVGPUTarget::SelectDMAFences +// override. A GLOBAL -> SHARED copy emits a producer GLOBAL release (before +// the copy) and a consumer SHARED acquire (after the wait, before threads +// read the shared data). A SHARED -> GLOBAL copy after a thread write emits +// a producer SHARED release. AUTO scope resolves from the space (GLOBAL -> +// device, SHARED -> group). + +__co__ auto g2s_read(f32 [1024] input) { + f32 [input.span(0)] output; + parallel p by 1 { + fa = dma.copy.async input.chunkat(p) => shared; + wait fa; + foreach idx in [1024] { + output.at(idx) = fa.data.at(idx); + } + } + return output; +} +// CHECK: coir.kernel @g2s_read +// CHECK: coir.fence +// CHECK: coir.dma.copy +// CHECK: coir.wait +// CHECK: coir.fence + +__co__ auto s2g_write(f32 [1024] input) { + f32 [input.span(0)] output; + parallel p by 1 { + shared f32 [1024] sbuf; + sbuf.at(0) = 1.0; + dma.copy sbuf => output.chunkat(p); + } + return output; +} +// CHECK: coir.kernel @s2g_write +// CHECK: coir.fence +// CHECK: coir.dma.copy +// CHECK: coir.wait +// CHECK: coir.return