Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
Language: Cpp
BasedOnStyle: LLVM

# 4 spaces everywhere
IndentWidth: 4
TabWidth: 4
UseTab: Never
ContinuationIndentWidth: 4

# Modern C++ style
Standard: c++20
ColumnLimit: 120
PointerAlignment: Left

# Organize includes
SortIncludes: true
IncludeBlocks: Regroup
15 changes: 15 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
If:
PathMatch: (^|.*/)crates/bender-slang/cpp/.*\.(h|hpp|hh|c|cc|cpp|cxx)$
CompileFlags:
Add:
- -std=c++20
- -fno-cxx-modules
- -I.
- -I../../../crates
- -I../vendor/slang/include
- -I../vendor/slang/external
- -I../../../target/slang-generated-include
- -I../../../target/cxxbridge
- -DSLANG_USE_MIMALLOC=1
- -DSLANG_USE_THREADS=1
- -DSLANG_BOOST_SINGLE_HEADER=1
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

jobs:
Expand Down
15 changes: 9 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.*
!/.ci/
!.git*
!.travis.yml
/target
/tests/tmp
# Cargo build files
target

# Temporary test files
tests/**/tmp
tests/**/.bender

# clangd
.cache/clangd
36 changes: 36 additions & 0 deletions crates/bender-slang/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
// Copyright (c) 2025 ETH Zurich
// Tim Fischer <fischeti@iis.ee.ethz.ch>

#[cfg(unix)]
// We create a symlink from the generated include directory to a stable location in the target directory
// so that tools like clangd can find the headers without needing to know the exact OUT_DIR path.
// This is purely for improving the development experience and is not necessary for the build itself.
fn refresh_include_symlink(generated_include_dir: &std::path::Path) {
use std::ffi::OsStr;
use std::fs;
use std::os::unix::fs::symlink;
use std::path::PathBuf;

let Ok(out_dir) = std::env::var("OUT_DIR") else {
return;
};
let out_dir = PathBuf::from(out_dir);

let Some(target_root) = out_dir
.ancestors()
.find(|path| path.file_name() == Some(OsStr::new("target")))
else {
return;
};

let stable_link = target_root.join("slang-generated-include");
let _ = fs::remove_file(&stable_link);
let _ = fs::remove_dir_all(&stable_link);
let _ = symlink(generated_include_dir, &stable_link);
}

#[cfg(not(unix))]
fn refresh_include_symlink(_generated_include_dir: &std::path::Path) {}

fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
Expand Down Expand Up @@ -64,6 +95,11 @@ fn main() {
let dst = slang_lib.build();
let lib_dir = dst.join("lib");

// Create a symlink for the generated include directory
if target_os == "linux" || target_os == "macos" {
refresh_include_symlink(&dst.join("include"));
}

// Configure Linker to find Slang static library
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=svlang");
Expand Down
3 changes: 2 additions & 1 deletion crates/bender-slang/cpp/slang_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
#include "rust/cxx.h"
#include "slang/diagnostics/DiagnosticEngine.h"
#include "slang/diagnostics/TextDiagnosticClient.h"
#include "slang/driver/Driver.h"
#include "slang/parsing/Preprocessor.h"
#include "slang/syntax/SyntaxTree.h"
#include "slang/text/SourceManager.h"

#include <cstddef>
#include <cstdint>
Expand Down
96 changes: 96 additions & 0 deletions crates/bender-slang/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::path::PathBuf;

fn fixture_path(rel: &str) -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("tests/pickle")
.join(rel)
.canonicalize()
.expect("valid fixture path")
.to_string_lossy()
.into_owned()
}

#[test]
fn parse_valid_file_succeeds() {
let mut session = bender_slang::SlangSession::new();
let files = vec![fixture_path("src/top.sv")];
let includes = vec![fixture_path("include")];
let defines = vec![];
assert!(session.parse_group(&files, &includes, &defines).is_ok());
assert_eq!(session.tree_count(), 1);
}

#[test]
fn parse_invalid_file_returns_parse_error() {
let mut session = bender_slang::SlangSession::new();
let files = vec![fixture_path("src/broken.sv")];
let includes = vec![];
let defines = vec![];
let result = session.parse_group(&files, &includes, &defines);

match result {
Err(bender_slang::SlangError::ParseGroup { .. }) => {}
Err(other) => panic!("expected SlangError::ParseGroup, got {other}"),
Ok(_) => panic!("expected parse to fail"),
}
}

#[test]
fn rewriter_build_from_trees_is_repeatable() {
let mut session = bender_slang::SlangSession::new();
let files = vec![fixture_path("src/top.sv")];
let includes = vec![fixture_path("include")];
let defines = vec![];
session
.parse_group(&files, &includes, &defines)
.expect("parse should succeed");

let trees = session.all_trees().expect("tree collection should succeed");
let mut rewriter_once = bender_slang::SyntaxTreeRewriter::new();
rewriter_once.set_prefix("p_");
rewriter_once.set_suffix("_s");
let first_pass_trees: Vec<_> = trees
.iter()
.map(|t| rewriter_once.rewrite_declarations(t))
.collect();
let renamed_once = rewriter_once.rewrite_references(
first_pass_trees
.first()
.expect("one first-pass tree expected"),
);
assert!(
renamed_once
.display(bender_slang::SlangPrintOpts {
expand_macros: false,
include_directives: true,
include_comments: true,
squash_newlines: false,
})
.contains("module p_top_s (")
);

// Rebuilding with the same trees should remain stable.
let mut rewriter_twice = bender_slang::SyntaxTreeRewriter::new();
rewriter_twice.set_prefix("p_");
rewriter_twice.set_suffix("_s");
let first_pass_trees: Vec<_> = trees
.iter()
.map(|t| rewriter_twice.rewrite_declarations(t))
.collect();
let renamed_twice = rewriter_twice.rewrite_references(
first_pass_trees
.first()
.expect("one first-pass tree expected"),
);
assert!(
renamed_twice
.display(bender_slang::SlangPrintOpts {
expand_macros: false,
include_directives: true,
include_comments: true,
squash_newlines: false,
})
.contains("module p_top_s (")
);
}
3 changes: 3 additions & 0 deletions tests/cli_regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,8 @@ regression_tests! {
packages: &["packages"],
packages_graph: &["packages", "--graph"],
packages_flat: &["packages", "--flat"],
// Enable once the golden binary is built with `slang` support.
// pickle_basic: &["pickle", "--target", "top"],
// pickle_top_trim: &["pickle", "--target", "top", "--top", "top"],

}
Loading