diff --git a/ClangAstParser/clang-dumper-release.tag b/ClangAstParser/clang-dumper-release.tag
index 73e74e98f..91967a831 100644
--- a/ClangAstParser/clang-dumper-release.tag
+++ b/ClangAstParser/clang-dumper-release.tag
@@ -1 +1 @@
-v18.1.8_2
+v18.1.8_4
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
index 041f66e56..daf9ae46b 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
@@ -363,9 +363,14 @@ private File findSystemClangResourceDir(Integer llvmMajor) {
var expectedVersion = llvmMajor == null ? "the local clang-dumper build's version"
: "LLVM " + llvmMajor;
+ var installHint = llvmMajor == null ? "clang++" : "clang++-" + llvmMajor;
throw new RuntimeException("Could not find a system Clang resource directory for SYSTEM mode with built-in CUDA"
+ " on host '" + SupportedPlatform.getCurrentPlatform() + "' (expected " + expectedVersion
- + "). Tried: " + commandNames);
+ + "). Tried: " + commandNames
+ + ". SYSTEM mode does not bundle Clang's CUDA wrapper headers, so a matching system Clang"
+ + " installation is required: install '" + installHint + "' (e.g. 'apt install " + installHint
+ + "' or 'brew install llvm'), or set the libc mode to 'builtin' to use the bundled includes"
+ + " instead of the system libc");
}
private static List getSystemClangCommandNames(Integer llvmMajor) {
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/CudaResources.java b/ClangAstParser/src/pt/up/fe/specs/clang/CudaResources.java
index 28641b5ab..30ea7ecc0 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/CudaResources.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/CudaResources.java
@@ -57,7 +57,8 @@
* the bundled dumper.
*
* CUDA resources are release-addressed. The published release directory is the complete CUDA installation;
- * manifests and downloaded archives used to build it live in staging until that directory is published.
+ * manifests and downloaded archives used to build it live in staging until that directory is published. The
+ * redistribution manifest itself is cached at {@code /cuda/redistrib_.json} and reused across calls.
*/
final class CudaResources {
@@ -117,7 +118,8 @@ static boolean isSupportedPlatform(Path cacheRoot) {
}
static boolean isSupportedPlatform(Path cacheRoot, FileResourceProvider manifestResource) {
- return findSupportedPlatform(getCurrentManifest(cacheRoot, manifestResource)).isPresent();
+ return findSupportedPlatform(readManifest(cacheRoot, ClangAstWebResource.getCudaReleaseTag(),
+ manifestResource)).isPresent();
}
static CudaPlatform getCurrentPlatform() {
@@ -125,7 +127,8 @@ static CudaPlatform getCurrentPlatform() {
}
static CudaPlatform getCurrentPlatform(Path cacheRoot) {
- return requireSupportedPlatform(getCurrentManifest(cacheRoot));
+ var releaseTag = ClangAstWebResource.getCudaReleaseTag();
+ return requireSupportedPlatform(readManifest(cacheRoot, releaseTag, getManifestResource(releaseTag)));
}
static CudaPlatform getCurrentPlatform(NvidiaCudaManifest manifest) {
@@ -183,30 +186,17 @@ private static RuntimeException unsupportedPlatform(NvidiaCudaManifest manifest,
+ ". Available manifest platform keys: " + getAvailablePlatformKeys(manifest));
}
- private static NvidiaCudaManifest getCurrentManifest(Path cacheRoot) {
- var releaseTag = ClangAstWebResource.getCudaReleaseTag();
- return getCurrentManifest(cacheRoot, releaseTag, getManifestResource(releaseTag));
- }
-
- private static NvidiaCudaManifest getCurrentManifest(Path cacheRoot, FileResourceProvider manifestResource) {
- var releaseTag = ClangAstWebResource.getCudaReleaseTag();
- return getCurrentManifest(cacheRoot, releaseTag, manifestResource);
- }
-
- private static NvidiaCudaManifest getCurrentManifest(Path cacheRoot, String releaseTag,
- FileResourceProvider manifestResource) {
- var cudaRoot = cacheRoot.resolve(CUDA_FOLDERNAME);
- CacheFiles.deleteUnlockedStagingLocks(cacheRoot, cudaRoot);
- var stagingDirectory = CacheFiles.createStagingDirectory(cacheRoot, cudaRoot, "." + releaseTag + ".tmp-");
- try {
- return downloadManifest(cacheRoot, stagingDirectory.path(), releaseTag, manifestResource);
- } finally {
- try {
- CacheFiles.delete(stagingDirectory.path());
- } finally {
- stagingDirectory.close();
- }
- }
+ // The manifest is installed to a stable cache path and reused across calls, so platform checks and the like do
+ // not re-download it on every invocation. Mirrors how ClangAstWebResource.getManifest caches the clang-dumper
+ // release manifest.
+ private static NvidiaCudaManifest readManifest(Path cacheRoot, String releaseTag,
+ FileResourceProvider manifestResource) {
+ var manifestFile = CacheFiles.installFile(cacheRoot,
+ cacheRoot.resolve(CUDA_FOLDERNAME).resolve(getManifestFilename(releaseTag)).toFile(),
+ manifestResource, null, "NVIDIA CUDA redistribution manifest");
+ var manifest = parseManifest(SpecsIo.read(manifestFile));
+ validateManifest(manifest, releaseTag);
+ return manifest;
}
private static boolean isCompatiblePlatform(String manifestPlatform, SupportedPlatform hostPlatform,
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ClavaDataParsers.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ClavaDataParsers.java
index 531c6feea..2e3c7c9ff 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ClavaDataParsers.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ClavaDataParsers.java
@@ -60,6 +60,7 @@
import pt.up.fe.specs.clava.ast.expr.data.designator.Designator;
import pt.up.fe.specs.clava.ast.expr.data.designator.FieldDesignator;
import pt.up.fe.specs.clava.ast.expr.data.offsetof.OffsetOfArray;
+import pt.up.fe.specs.clava.ast.expr.data.offsetof.OffsetOfBase;
import pt.up.fe.specs.clava.ast.expr.data.offsetof.OffsetOfComponent;
import pt.up.fe.specs.clava.ast.expr.data.offsetof.OffsetOfComponentKind;
import pt.up.fe.specs.clava.ast.expr.data.offsetof.OffsetOfField;
@@ -459,6 +460,9 @@ public static OffsetOfComponent offsetOfComponent(LineStream lines, ClangAstData
case IDENTIFIER:
component.set(OffsetOfIdentifier.FIELD_NAME, lines.nextLine());
break;
+ case BASE:
+ parserData.getClavaNodes().queueSetNode(component, OffsetOfBase.TYPE, lines.nextLine());
+ break;
default:
throw new NotImplementedException(kind);
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/OffsetOfExpr.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/OffsetOfExpr.java
index bf5ab5083..2ef7f313f 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/OffsetOfExpr.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/OffsetOfExpr.java
@@ -50,18 +50,20 @@ private String getComponentsCode() {
StringBuilder code = new StringBuilder();
- boolean isFirst = true;
+ boolean hasCode = false;
for (OffsetOfComponent component : get(COMPONENTS)) {
+ String componentCode = component.getCode();
- if (component.isField() && !isFirst) {
- code.append(".");
+ if (componentCode.isEmpty()) {
+ continue;
}
- code.append(component.getCode());
-
- if (isFirst) {
- isFirst = false;
+ if (component.isField() && hasCode) {
+ code.append(".");
}
+
+ code.append(componentCode);
+ hasCode = true;
}
return code.toString();
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfBase.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfBase.java
new file mode 100644
index 000000000..b52ecf056
--- /dev/null
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfBase.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clava.ast.expr.data.offsetof;
+
+import org.suikasoft.jOptions.Datakey.DataKey;
+import org.suikasoft.jOptions.Datakey.KeyFactory;
+
+import pt.up.fe.specs.clava.ast.type.Type;
+
+public class OffsetOfBase extends OffsetOfComponent {
+
+ /// DATAKEYS BEGIN
+
+ public final static DataKey TYPE = KeyFactory.object("type", Type.class);
+
+ /// DATAKEYS END
+
+ @Override
+ public String getCode() {
+ // Clang adds the base to the semantic path, but it is not part of the source designator.
+ return "";
+ }
+
+ @Override
+ public OffsetOfComponentKind getKind() {
+ return OffsetOfComponentKind.BASE;
+ }
+}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfComponent.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfComponent.java
index bfe9330d5..bcd2765a8 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfComponent.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/data/offsetof/OffsetOfComponent.java
@@ -41,6 +41,8 @@ public static OffsetOfComponent newInstance(OffsetOfComponentKind kind) {
return new OffsetOfField();
case IDENTIFIER:
return new OffsetOfIdentifier();
+ case BASE:
+ return new OffsetOfBase();
default:
throw new NotImplementedException(kind);
}