From f91c9f8ba7484d9802d2c512fb8943927646d428 Mon Sep 17 00:00:00 2001 From: Julie Schwartz Date: Mon, 15 Jun 2026 23:27:51 +1200 Subject: [PATCH 1/7] CI: Update the GitHub runners Add macos-26-intel, ubuntu-26.04, ubuntu-24.04-arm, ubuntu-26.04-arm --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32f6a90cb..33209d2f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: build-and-test-ubuntu: strategy: matrix: - os: [ ubuntu-22.04, ubuntu-24.04 ] + os: [ ubuntu-22.04, ubuntu-24.04, ubuntu-24.04-arm, ubuntu-26.04, ubuntu-26.04-arm ] timeout_override: [ false ] fail-fast: false name: "Build/Test: ${{ matrix.os }}" @@ -58,7 +58,7 @@ jobs: build-and-test-macos: strategy: matrix: - os: [ macos-14, macos-15, macos-15-intel, macos-26 ] + os: [ macos-14, macos-15, macos-15-intel, macos-26, macos-26-intel ] timeout_override: [ false ] brew_update: [ true ] fail-fast: false @@ -124,7 +124,7 @@ jobs: build-doc-ubuntu: strategy: matrix: - os: [ ubuntu-22.04, ubuntu-24.04 ] + os: [ ubuntu-22.04, ubuntu-24.04, ubuntu-24.04-arm, ubuntu-26.04, ubuntu-26.04-arm ] fail-fast: false name: "Build doc: ${{ matrix.os }}" runs-on: ${{ matrix.os }} @@ -162,7 +162,7 @@ jobs: build-doc-macOS: strategy: matrix: - os: [ macos-14, macos-15, macos-15-intel, macos-26 ] + os: [ macos-14, macos-15, macos-15-intel, macos-26, macos-26-intel ] brew_update: [ true ] fail-fast: false name: "Build doc: ${{ matrix.os }}" From 506a71ecbb0a626dbcc9beaa746f18994819c845 Mon Sep 17 00:00:00 2001 From: Julie Schwartz Date: Tue, 16 Jun 2026 11:47:12 +1200 Subject: [PATCH 2/7] CI: Install GHC/GHCup using the haskell/ghcup-setup Action This avoids assuming that GHCup is installed on the runner (which it is not, for GitHub's Ubuntu ARM runners). It replaces shell code that had been used for a similar situation for macOS runners. --- .github/workflows/build-and-test-macos.yml | 16 +++++++--------- .github/workflows/build-and-test-ubuntu.yml | 10 +++++++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-and-test-macos.yml b/.github/workflows/build-and-test-macos.yml index 297406ebf..7dc82e8e3 100644 --- a/.github/workflows/build-and-test-macos.yml +++ b/.github/workflows/build-and-test-macos.yml @@ -39,15 +39,13 @@ jobs: BREW_UPDATE: ${{ inputs.brew_update }} run: | .github/workflows/install_dependencies_macos.sh - # If the runner doesn't have 'ghcup', install it - if ! [ -x "$(command -v ghcup)" ]; then - curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh - echo "$HOME/.ghcup/bin" >> $GITHUB_PATH - export PATH=$HOME/.ghcup/bin:$PATH - fi - # Don't rely on the VM to pick the GHC version - ghcup install ghc ${{ inputs.ghc_version }} - ghcup set ghc ${{ inputs.ghc_version }} + # Don't rely on the VM to pick the GHC version + # (and don't assume that GHCup is already installed on the runner) + - name: Install GHC + uses: haskell/ghcup-setup@v1 + with: + ghc: ${{ inputs.ghc_version }} + cabal: recommended # Until BSC uses cabal to build, pre-install these packages - name: Install Haskell dependencies shell: bash diff --git a/.github/workflows/build-and-test-ubuntu.yml b/.github/workflows/build-and-test-ubuntu.yml index 3459257e5..73c20054c 100644 --- a/.github/workflows/build-and-test-ubuntu.yml +++ b/.github/workflows/build-and-test-ubuntu.yml @@ -33,9 +33,13 @@ jobs: shell: bash run: | sudo .github/workflows/install_dependencies_ubuntu.sh - # Don't rely on the VM to pick the GHC version - ghcup install ghc ${{ inputs.ghc_version }} - ghcup set ghc ${{ inputs.ghc_version }} + # Don't rely on the VM to pick the GHC version + # (and don't assume that GHCup is already installed on the runner) + - name: Install GHC + uses: haskell/ghcup-setup@v1 + with: + ghc: ${{ inputs.ghc_version }} + cabal: recommended # Until BSC uses cabal to build, pre-install these packages - name: Install Haskell dependencies shell: bash From 49382771b4052a0f9e732859272a132f7738e8fd Mon Sep 17 00:00:00 2001 From: Julie Schwartz Date: Tue, 16 Jun 2026 16:01:16 +1200 Subject: [PATCH 3/7] DivMod test: Expect either arm64 or aarch64 for ARM mach type --- testsuite/bsc.misc/divmod/divmod.exp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/bsc.misc/divmod/divmod.exp b/testsuite/bsc.misc/divmod/divmod.exp index cc82f103c..339455b21 100644 --- a/testsuite/bsc.misc/divmod/divmod.exp +++ b/testsuite/bsc.misc/divmod/divmod.exp @@ -12,7 +12,7 @@ set fpe [list SIGFPE 8 136] # Test that divide-by-zero produces some failure in Bluesim compile_object_pass DivideByZero.bsv sysDivideByZero link_objects_pass sysDivideByZero sysDivideByZero -if [string equal [which_mach] "arm64"] { +if { [lsearch -exact -nocase {aarch64 arm64} [which_mach]] != -1 } { # This is an expected bug (GitHub #688) sim_output_status sysDivideByZero 0 } else { From b6a3abf190763bfab1f388cbe5d69a63019b29ef Mon Sep 17 00:00:00 2001 From: Julie Schwartz Date: Wed, 17 Jun 2026 15:48:05 +1200 Subject: [PATCH 4/7] Fix STP_STUB=1 build option to work as intended --- src/vendor/stp/src_stub/Makefile | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/vendor/stp/src_stub/Makefile b/src/vendor/stp/src_stub/Makefile index fe24c4b0e..8e1095b44 100644 --- a/src/vendor/stp/src_stub/Makefile +++ b/src/vendor/stp/src_stub/Makefile @@ -10,9 +10,13 @@ LIB_DIR=../lib INC_DIR=../include ifeq ($(OSTYPE), Darwin) -LDFLAGS = -dynamiclib -Wl,-install_name,libstp_stub.so +SNAME = libstp.dylib +LDFLAGS = -dynamiclib -Wl,-install_name,$(SNAME) +LINKCMD = : else -LDFLAGS = -shared -Wl,-soname,libstp_stub.so +SNAME = libstp.so.1 +LDFLAGS = -shared -Wl,-soname,$(SNAME) +LINKCMD = ln -fsn $(SNAME) $(LIB_DIR)/libstp.so endif .PHONY: all @@ -21,25 +25,20 @@ all: install stp_stub.o: stp_stub.c stp_c_interface.h $(CC) $(CFLAGS) -c -o $@ $< -libstp_stub.so: stp_stub.o +$(SNAME): stp_stub.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -libstp.so: libstp_stub.so - $(RM) $@ - ln -s $< $@ - -install: libstp_stub.so +install: $(SNAME) mkdir -p $(LIB_DIR) - $(CP) libstp_stub.so $(LIB_DIR)/ - ln -fsn libstp.so.1 $(LIB_DIR)/libstp.so - ln -fsn libstp_stub.so $(LIB_DIR)/libstp.so.1 + $(CP) $(SNAME) $(LIB_DIR)/ + $(LINKCMD) mkdir -p $(INC_DIR) $(CP) stp_c_interface.h $(INC_DIR)/ .PHONY: clean full_clean clean: - $(RM) *.o *.so + $(RM) *.o *.so *.dylib full_clean: clean $(RM) -R $(LIB_DIR) From e8db73bb18793da6e5d3d6106813fec9351dfbbd Mon Sep 17 00:00:00 2001 From: Julie Schwartz Date: Wed, 17 Jun 2026 15:52:56 +1200 Subject: [PATCH 5/7] CI: Disable STP for Ubuntu ARM builds --- .github/workflows/build-and-test-ubuntu.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-and-test-ubuntu.yml b/.github/workflows/build-and-test-ubuntu.yml index 73c20054c..be1536903 100644 --- a/.github/workflows/build-and-test-ubuntu.yml +++ b/.github/workflows/build-and-test-ubuntu.yml @@ -69,6 +69,11 @@ jobs: # Generate package info export BSC_BUILD_PKGINFO=1 + # An STP test hangs on the ARM runners + if [[ "${{ inputs.os }}" == *-arm ]]; then + export STP_STUB=1 + fi + make -j ${CORES} GHCJOBS=2 GHCRTSFLAGS='+RTS -M5G -A128m -RTS' install-src tar czf inst.tar.gz inst - name: CCache stats From 1f9e19d9c89c41c6a351377a7505c9f6a1cf784e Mon Sep 17 00:00:00 2001 From: Amy Ringo Date: Thu, 25 Jun 2026 20:27:59 +0000 Subject: [PATCH 6/7] ISyntax: make splitITAp O(n) instead of O(n^2) splitITAp accumulated arguments with `as ++ [a]` while recursing down the type-application spine. That is O(n^2) in the spine length and reallocates the argument list at every level (~21% of allocation when compiling wide split-port types). Use a reverse-accumulator (descend the spine prepending args) so it is O(n) with no repeated appends. Result is identical. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/comp/ISyntax.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/comp/ISyntax.hs b/src/comp/ISyntax.hs index 2078d1d47..09ea35e2f 100644 --- a/src/comp/ISyntax.hs +++ b/src/comp/ISyntax.hs @@ -440,9 +440,9 @@ fTVars (ITStr _) = S.empty splitITAp :: IType -> (IType, [IType]) -splitITAp (ITAp f a) = let (l, as) = splitITAp f - in (l, as ++ [a]) -splitITAp t = (t, []) +splitITAp t0 = go t0 [] + where go (ITAp f a) acc = go f (a : acc) + go t acc = (t, acc) -- ============================== From 4bc48fa8cf5c71c9f239d18be6e28fa457e83e44 Mon Sep 17 00:00:00 2001 From: Amy Ringo Date: Thu, 25 Jun 2026 20:27:49 +0000 Subject: [PATCH 7/7] typecheck: make isTFun O(log n) via a precomputed Set isTFun (and PredTrie.isTFunTyCon) tested membership with `i `elem` numOpNames ++ strOpNames`, which rebuilds the (10-element) list and does a linear scan of idEq comparisons on every call. isTFun is called once per type constructor during type expansion/normalization, so on wide types it dominates: profiling a 521-port always_ready struct method (a port-shape boundary module) showed id_fs/idEq/isTFun as the top cost centres, ~30% of allocation. Precompute the operator-name set once and use Set membership instead. Semantics-preserving. On the struct-form module above this cut the BscV elaboration ~12% (131.3s -> 115.6s, isolated). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/comp/Pred.hs | 5 +---- src/comp/PredTrie.hs | 12 ++++++------ src/comp/TypeOps.hs | 9 ++++++++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/comp/Pred.hs b/src/comp/Pred.hs index d486e0a16..d0e033f5e 100644 --- a/src/comp/Pred.hs +++ b/src/comp/Pred.hs @@ -260,13 +260,10 @@ expandSyn t0 = exp [] f as -- Type functions (TIatf) are NOT expanded here; they are handled by -- normT via expTFun, which generates the appropriate class constraint. exp _ f@(TCon (TyCon i _ _)) as - | isTFun i = apTFun f i $ map expandSyn as + | isPrimTFunName i = apTFun f i $ map expandSyn as -- f does not contain a TAp or a synonym TCon, so it cannot have synonyms to expand exp _ f as = foldl TAp f $ map expandSyn as -isTFun :: Id -> Bool -isTFun i = i `elem` numOpNames ++ strOpNames - apTFun :: Type -> Id -> [Type] -> Type apTFun _ i [TCon (TyNum x px), TCon (TyNum y py)] | Just n <- opNumT i [x, y] = TCon (TyNum n p') where p' = bestPosition px py diff --git a/src/comp/PredTrie.hs b/src/comp/PredTrie.hs index 7b8bc5db2..8bcee73de 100644 --- a/src/comp/PredTrie.hs +++ b/src/comp/PredTrie.hs @@ -20,7 +20,7 @@ import qualified Data.Set as S import Error(internalError) import CType(Type(..), TyCon(..), TyVar, leftTyCon) -import TypeOps(numOpNames, strOpNames) +import TypeOps(isPrimTFunName) import Pred(Pred(..), Class(inputPositions), expandSyn) -- --------------------------------------------------------------------------- @@ -166,7 +166,7 @@ predTrieKey positions (IsIn _ ts) = -- query that finds all instances that might overlap with it. Variable positions -- in the instance key (Nothing) become Free so that cross-branch overlaps are -- found; concrete positions (Just tc) become Con tc. --- Unlike predQuery this does not apply isTFunTyCon: for overlap detection we +-- Unlike predQuery this does not apply isPrimTFunTyCon: for overlap detection we -- want to find all candidates including those with numeric-literal heads. overlapProbeQuery :: Pred -> [QueryElem] overlapProbeQuery p@(IsIn c _) = @@ -193,9 +193,9 @@ predQuery btvs (IsIn c ts) = -- concrete constructor at query time, so queries at those positions must -- probe ALL trie branches (Free) to avoid missing instances that have a -- concrete-number head (e.g. VectorTreeReduce 1, VectorTreeReduce 2). -isTFunTyCon :: TyCon -> Bool -isTFunTyCon (TyCon { tcon_name = i }) = i `elem` numOpNames ++ strOpNames -isTFunTyCon _ = False +isPrimTFunTyCon :: TyCon -> Bool +isPrimTFunTyCon (TyCon { tcon_name = i }) = isPrimTFunName i +isPrimTFunTyCon _ = False -- | Classify one type argument of the predicate being resolved. -- A type-function head (TAdd, TLog, etc.) is treated as Free so that @@ -204,7 +204,7 @@ isTFunTyCon _ = False mkQueryElem :: S.Set TyVar -> Type -> QueryElem mkQueryElem btvs t = case leftTyCon t of - Just tc | not (isTFunTyCon tc) -> Con tc + Just tc | not (isPrimTFunTyCon tc) -> Con tc Just _ -> Free -- unevaluated type function Nothing -> case headVar t of Just tv | tv `S.member` btvs -> Bound diff --git a/src/comp/TypeOps.hs b/src/comp/TypeOps.hs index c4c1fe2d6..033f1ac42 100644 --- a/src/comp/TypeOps.hs +++ b/src/comp/TypeOps.hs @@ -1,11 +1,18 @@ -module TypeOps(opNumT, numOpNames, opStrT, strOpNames) where +module TypeOps(isPrimTFunName, opNumT, opStrT) where -- common routines for handling numeric and string types +import qualified Data.Set as S import Id import PreIds(idTAdd, idTSub, idTMul, idTDiv, idTLog, idTExp, idTMax, idTMin, idTStrCat, idTNumToStr) import Util(divC, log2) import FStringCompat(FString, concatFString) +isPrimTFunName :: Id -> Bool +isPrimTFunName i = i `S.member` primTFunNames + +primTFunNames :: S.Set Id +primTFunNames = S.fromList (numOpNames ++ strOpNames) + -- do a numeric type operation on a list of arguments -- note that we have to validate that the result is going to -- to be >= 0 - otherwise it isn't a valid numeric type