diff --git a/source_.jar b/source_.jar index bd148087..c025f259 100644 Binary files a/source_.jar and b/source_.jar differ diff --git a/src/main/java/cod/ir/IRManager.java b/src/main/java/cod/ir/IRManager.java index 943988ed..4b4c2524 100644 --- a/src/main/java/cod/ir/IRManager.java +++ b/src/main/java/cod/ir/IRManager.java @@ -5,14 +5,34 @@ import cod.ptac.Compiler; import cod.ptac.Unit; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import java.util.zip.CRC32; public class IRManager { private static final String BIN_DIR = "bin"; private static final String IR_EXT = ".codb"; + private static final String CONTAINER_EXT = ".codc"; + private static final int BUFFER_SIZE = 8192; + private static final Map CONTAINER_LOCKS = new ConcurrentHashMap(); private final String projectRoot; private final IRWriter writer; @@ -43,13 +63,17 @@ public Type load(String unit, String className) { } } - File file = getIRFile(unit, className); - if (!file.exists()) { - return null; - } - try { - Artifact artifact = reader.readArtifact(file); + Artifact artifact = readArtifactFromContainer(unit, className); + if (artifact == null) { + // Standalone .codb files are a permanent supported format. + // .codc containers are additive grouping, not a replacement. + File file = getIRFile(unit, className); + if (!file.exists()) { + return null; + } + artifact = reader.readArtifact(file); + } if (artifact != null) { putArtifactCache(unit, className, artifact); Type type = artifact.typeSnapshot; @@ -68,10 +92,9 @@ public void save(String unit, Type type) { if (type == null || unit == null || type.name == null) { return; } - File file = getIRFile(unit, type.name); try { Artifact artifact = compiler.compile(unit, type); - writer.writeArtifact(file, artifact); + writeArtifactToContainer(unit, artifact.className, artifact); putCache(unit, type.name, type); putArtifactCache(unit, type.name, artifact); } catch (IOException ignored) {} @@ -87,13 +110,17 @@ public Artifact loadArtifact(String unit, String className) { return unitCache.get(className); } - File file = getIRFile(unit, className); - if (!file.exists()) { - return null; - } - try { - Artifact artifact = reader.readArtifact(file); + Artifact artifact = readArtifactFromContainer(unit, className); + if (artifact == null) { + // Standalone .codb files are a permanent supported format. + // .codc containers are additive grouping, not a replacement. + File file = getIRFile(unit, className); + if (!file.exists()) { + return null; + } + artifact = reader.readArtifact(file); + } if (artifact != null) { putArtifactCache(unit, className, artifact); if (artifact.typeSnapshot != null) { @@ -113,9 +140,8 @@ public Unit loadCodPTACUnit(String unit, String className) { public void saveArtifact(String unit, Artifact artifact) { if (artifact == null || unit == null || artifact.className == null) return; - File file = getIRFile(unit, artifact.className); try { - writer.writeArtifact(file, artifact); + writeArtifactToContainer(unit, artifact.className, artifact); putArtifactCache(unit, artifact.className, artifact); if (artifact.typeSnapshot != null) { putCache(unit, artifact.className, artifact.typeSnapshot); @@ -166,4 +192,176 @@ private File getIRFile(String unit, String className) { String path = projectRoot + "/src/" + BIN_DIR + "/" + unit + "/" + className + IR_EXT; return new File(path); } + + private File getContainerFile(String unit) { + String rootUnit = getRootUnit(unit); + String path = projectRoot + "/src/" + BIN_DIR + "/" + rootUnit + CONTAINER_EXT; + return new File(path); + } + + private String getRootUnit(String unit) { + if (unit == null || unit.isEmpty()) return "default"; + int dot = unit.indexOf('.'); + if (dot < 0) return unit; + if (dot == 0) return "default"; + return unit.substring(0, dot); + } + + private String getContainerEntryName(String unit, String className) { + return unit + "/" + className + IR_EXT; + } + + private Artifact readArtifactFromContainer(String unit, String className) throws IOException { + File container = getContainerFile(unit); + if (!container.exists() || !container.isFile()) { + return null; + } + + String targetEntry = getContainerEntryName(unit, className); + ZipInputStream in = null; + try { + in = new ZipInputStream(new BufferedInputStream(new FileInputStream(container))); + ZipEntry entry; + while ((entry = in.getNextEntry()) != null) { + if (!entry.isDirectory() && targetEntry.equals(entry.getName())) { + byte[] data = readAllBytes(in); + return readArtifactFromBytes(data); + } + } + return null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) {} + } + } + } + + private void writeArtifactToContainer(String unit, String className, Artifact artifact) throws IOException { + if (unit == null || className == null || artifact == null) return; + + File container = getContainerFile(unit); + File parent = container.getParentFile(); + if (parent != null && !parent.exists() && !parent.mkdirs()) { + throw new IOException("Failed to create IR container directory: " + parent.getAbsolutePath()); + } + + Object containerLock = getContainerLock(container); + synchronized (containerLock) { + Map entries = readContainerEntries(container); + entries.put(getContainerEntryName(unit, className), writeArtifactToBytes(artifact)); + + File temp = new File(container.getAbsolutePath() + ".tmp"); + ZipOutputStream out = null; + boolean moved = false; + try { + out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(temp))); + out.setLevel(0); + for (Map.Entry e : entries.entrySet()) { + byte[] value = e.getValue(); + CRC32 crc = new CRC32(); + crc.update(value); + ZipEntry zipEntry = new ZipEntry(e.getKey()); + // .codc is intentionally an uncompressed zip container (level 0, STORED entries). + zipEntry.setMethod(ZipEntry.STORED); + zipEntry.setSize(value.length); + zipEntry.setCompressedSize(value.length); + zipEntry.setCrc(crc.getValue()); + out.putNextEntry(zipEntry); + out.write(value); + out.closeEntry(); + } + out.finish(); + Files.move(temp.toPath(), container.toPath(), StandardCopyOption.REPLACE_EXISTING); + moved = true; + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException ignored) {} + } + if (!moved && temp.exists()) { + try { + Files.delete(temp.toPath()); + } catch (IOException ignored) {} + } + } + } + } + + private Object getContainerLock(File container) { + String key = container.getAbsolutePath(); + Object lock = CONTAINER_LOCKS.get(key); + if (lock != null) return lock; + Object created = new Object(); + Object existing = CONTAINER_LOCKS.putIfAbsent(key, created); + return existing != null ? existing : created; + } + + private Map readContainerEntries(File container) throws IOException { + Map entries = new LinkedHashMap<>(); + if (container == null || !container.exists() || !container.isFile()) { + return entries; + } + + ZipInputStream in = null; + try { + in = new ZipInputStream(new BufferedInputStream(new FileInputStream(container))); + ZipEntry entry; + while ((entry = in.getNextEntry()) != null) { + if (entry.isDirectory()) continue; + entries.put(entry.getName(), readAllBytes(in)); + } + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) {} + } + } + return entries; + } + + private byte[] writeArtifactToBytes(Artifact artifact) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream out = null; + try { + out = new DataOutputStream(baos); + IRArtifactCodec.writeArtifact(out, artifact); + out.flush(); + return baos.toByteArray(); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException ignored) {} + } + } + } + + private Artifact readArtifactFromBytes(byte[] data) throws IOException { + if (data == null) return null; + DataInputStream in = null; + try { + in = new DataInputStream(new ByteArrayInputStream(data)); + return IRArtifactCodec.readArtifact(in); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) {} + } + } + } + + private byte[] readAllBytes(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int read; + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + } + return out.toByteArray(); + } } diff --git a/src/main/java/cod/runner/CommandRunner.java b/src/main/java/cod/runner/CommandRunner.java index d6e89ed3..f43ba78e 100644 --- a/src/main/java/cod/runner/CommandRunner.java +++ b/src/main/java/cod/runner/CommandRunner.java @@ -139,7 +139,7 @@ private void handleCompileCommand(String[] args) throws Exception { int compiled = 0; for (Type type : ast.unit.types) { bm.save(ast.unit.name, type); - System.out.println("Compiled (CodP-TAC artifact): " + type.name + " → " + type.name + ".codb"); + System.out.println("Compiled (CodP-TAC artifact): " + type.name + " → .codc/" + ast.unit.name + "/" + type.name + ".codb"); compiled++; } @@ -210,7 +210,7 @@ private void executeInterpretation(Program ast) { } /** - * Compile all classes in the program to .codb IR files + * Compile all classes in the program to .codc IR container entries */ private void compileToBytecode(Program ast) { if (ast == null || ast.unit == null || irManager == null) { @@ -227,7 +227,7 @@ private void compileToBytecode(Program ast) { try { irManager.save(unitName, type); compiled++; - DebugSystem.debug(NAME + LOG_TAG, "Compiled CodP-TAC artifact: " + type.name + " → " + type.name + ".codb"); + DebugSystem.debug(NAME + LOG_TAG, "Compiled CodP-TAC artifact: " + type.name + " → .codc/" + unitName + "/" + type.name + ".codb"); } catch (Exception e) { DebugSystem.warn(NAME + LOG_TAG, "Failed to compile " + type.name + ": " + e.getMessage()); } @@ -252,7 +252,7 @@ private void printHelp() { out(" -h, --help Show this help message"); out(); out("Commands:"); - out(" compile Compile source to bytecode (.codb)"); + out(" compile Compile source to bytecode container (.codc with .codb entries)"); out("Environment flags:"); out(" COD_PTAC_MODE=interpreter|compile-only|compile-execute"); out(" COD_PTAC_FALLBACK=true|false"); diff --git a/src/main/java/cod/runner/TestRunner.java b/src/main/java/cod/runner/TestRunner.java index f46a57ff..4a533401 100644 --- a/src/main/java/cod/runner/TestRunner.java +++ b/src/main/java/cod/runner/TestRunner.java @@ -295,7 +295,7 @@ private void executeWithManualInterpreter(Program ast) { } /** - * Compile all classes in the program to .codb IR files + * Compile all classes in the program to .codc IR container entries */ private void compileToBytecode(Program ast) { if (ast == null || ast.unit == null || irManager == null) { @@ -312,7 +312,7 @@ private void compileToBytecode(Program ast) { try { irManager.save(unitName, type); compiled++; - DebugSystem.debug(NAME + LOG_TAG, "Compiled CodP-TAC artifact: " + type.name + " → " + type.name + ".codb"); + DebugSystem.debug(NAME + LOG_TAG, "Compiled CodP-TAC artifact: " + type.name + " → .codc/" + unitName + "/" + type.name + ".codb"); } catch (Exception e) { DebugSystem.warn(NAME + LOG_TAG, "Failed to compile " + type.name + ": " + e.getMessage()); } diff --git a/src/main/java/cod/semantic/ImportResolver.java b/src/main/java/cod/semantic/ImportResolver.java index b298bdca..10eb1d5c 100644 --- a/src/main/java/cod/semantic/ImportResolver.java +++ b/src/main/java/cod/semantic/ImportResolver.java @@ -736,7 +736,7 @@ public Type resolveImport(String importName) throws Exception { Artifact artifact = irManager.loadArtifact(unitName, className); if (artifact != null) { bytecodeCacheHits++; - DebugSystem.debug("IR", "Loaded " + className + " CodP-TAC artifact from .codb (cache hit)"); + DebugSystem.debug("IR", "Loaded " + className + " CodP-TAC artifact from .codc/.codb (cache hit)"); loadedArtifacts.put(importName, artifact); if (artifact.typeSnapshot != null) { loadedTypes.put(importName, artifact.typeSnapshot); @@ -744,7 +744,7 @@ public Type resolveImport(String importName) throws Exception { } } else { bytecodeCacheMisses++; - DebugSystem.debug("IR", ".codb not found for " + className + " (cache miss)"); + DebugSystem.debug("IR", ".codc/.codb artifact not found for " + className + " (cache miss)"); } } // ========== END CodP-TAC CHECK ========== @@ -771,7 +771,7 @@ public Type resolveImport(String importName) throws Exception { // Save IR for next time if (irManager != null) { irManager.save(unitName, type); - DebugSystem.debug("IR", "Saved " + className + " to .codb"); + DebugSystem.debug("IR", "Saved " + className + " to .codc/.codb"); } loadedTypes.put(importName, type); return type; @@ -908,7 +908,7 @@ private Type resolveImportByScan(String importName, String unitName, String clas // Save IR if (irManager != null) { irManager.save(unitName, type); - DebugSystem.debug("IR", "Saved " + className + " to .codb"); + DebugSystem.debug("IR", "Saved " + className + " to .codc/.codb"); } loadedTypes.put(importName, type);