Java: byte storage
This bounded Phase 4 change specializes Java byte storage. Host operations, library exports, SDK Java interoperability and remaining target byte storage are still separate assignments.
The emitter recognizes underlying uint8, including byte, non-generic
aliases, named elements, named slices and arrays. Byte arrays and slice
backing use native byte[]. Go integer values remain Java long: primitive
slice readers and array loads mask signed Java bytes with 255, and stores
narrow to byte after the existing Go integer operations. Generic slices and
arrays retain Object[] storage.
Immutable slice headers hold backing identity, offset, length and capacity.
A byte flag on BYTE_NIL preserves the representation through nil reslices,
empty spread append and zero-argument append. Allocated empty byte slices
have non-null zero-length arrays. make allocates and zeroes the complete
capacity, and native append growth allocates zeroed spare capacity. The
capacity rule remains max(needed, max(1, 2*cap)). Additions widen before
integer casts; size checks precede allocation and mutation. JVM allocations
retain the existing Integer.MAX_VALUE - 8 element limit and available-memory
constraint; exceeding host limits is an implementation fault.
Native append and copy use System.arraycopy, including overlapping views
in both directions. Spread append avoids a full-payload boxed temporary;
individual append arguments use primitive byte[] literals. Byte clear fills
only the selected view. Array assignment copies into existing backing so
array slices retain their alias; array value copies and slice-to-array value
conversion allocate independent native arrays. Array equality compares byte
values, and array map keys snapshot bytes in an immutable Latin-1 Java string.
Named array/interface type identity still comes from the existing type
descriptors. Go strings retain one Java character per source byte; byte
conversions copy and preserve NUL, high bytes and invalid UTF-8.
Slice.get/set provide scalar boxing only at generic boundaries, including
contract encoding. Generated byte index/store operations use primitive
bget/bset variants, including unsigned-index checking. Generic function
slice consumers and rune conversions use the same scalar accessor without
scheduler changes. Headers, arrays, struct fields, interfaces, closures and
suspended frames remain ordinary Java GC-reachable values. Element addresses,
slice-to-array pointer conversions and source generics remain outside the
accepted language subset.
Verification
Section titled “Verification”Run from the Goalchemy root. GOTOOLCHAIN selects the compiler’s Go toolchain;
the loader and actual native-Go oracle retain their pinned defaults.
GOTOOLCHAIN=go1.25.14 go run ./cmd/goalchemy spec validateGOTOOLCHAIN=go1.25.14 go run ./cmd/goalchemy spec generate -checkGOTOOLCHAIN=go1.25.14 go test ./tests/contracts -count=1 -vGOTOOLCHAIN=go1.25.14 go test ./internal/emit/... ./internal/driver ./internal/subset ./internal/specgen -count=1GOTOOLCHAIN=go1.25.14 GOALCHEMY_TEST_TARGETS=java go test ./tests/language -count=1 -vGOTOOLCHAIN=go1.25.14 GOALCHEMY_TEST_TARGETS=go,typescript,python,java,csharp,rust,c FIXTURE=byte_storage go test ./tests/language -count=1 -vThe default contract suite includes TestJavaByteStorage, which compiles the
common language fixture and then runs targets/java/tests/ByteStorageTest.java
against its emitted program and runtime. It asserts actual byte[] backing,
physical zeroed capacity, primitive unsigned reads, nil/empty behavior,
copy/append overlap, offsets, string copies, array-view aliasing, size errors,
and generic non-byte behavior. Reflection invokes emitted primitive array
zero/clone/set/equality/key helpers, so boxed arrays cannot pass solely by
matching program output. The suite checks emitted primitive paths too.
TestJavaByteGrowthNativeOracle always runs the independent native-Go growth
oracle; no environment opt-in is needed. Before this change, Java threw a
host NullPointerException when reading grown spare capacity. The fixed
observation is 4 255 128 3 0, followed by two zero lines. The historical
TypeScript assignment’s opt-in reproducer also now passes for Java:
GOTOOLCHAIN=go1.25.14 GOALCHEMY_BYTE_TARGETS=java go test ./tests/contracts -run TestTypeScriptByteGrowthNativeOracle -count=1 -vThe common byte_storage fixture compares real native-Go observations for
aliases/named types, full slices, growth detachment, arrays and struct copies,
escaped pointers, interface aliases, closures and arbitrary byte strings.
The separate native-Go differential java_byte_values fixture adds array
and interface map keys, high-byte reads/stores with uint64 indexes, huge
unsigned index rejection and byte arrays/slices across cooperative frame
and closure suspension. Generic slice, rune and function-slice behavior is
also exercised by the full Java language and all-seven contract suites.
Emitter/specgen packages currently contain no package-local tests; their
behavior is tested by these contract/language runs.
Memory and throughput
Section titled “Memory and throughput”Compile once, then start a fresh JVM for each observation:
mkdir -p out/java-byte-bench.toolchains/jdk-21.0.12.1+1/bin/javac -d out/java-byte-bench targets/java/types/*.java targets/java/runtime/*.java targets/java/tests/ByteStorageBench.java.toolchains/jdk-21.0.12.1+1/bin/java -Xms256m -Xmx1g -cp out/java-byte-bench ByteStorageBench native 2.toolchains/jdk-21.0.12.1+1/bin/java -Xms256m -Xmx1g -cp out/java-byte-bench ByteStorageBench boxed 2.toolchains/jdk-21.0.12.1+1/bin/java -Xms256m -Xmx1g -cp out/java-byte-bench ByteStorageBench native 8.toolchains/jdk-21.0.12.1+1/bin/java -Xms256m -Xmx1g -cp out/java-byte-bench ByteStorageBench boxed 8Measured on Linux x86_64, kernel 6.8.0-87-generic, AMD Ryzen 7 6800H,
16 logical CPUs, pinned Temurin 21.0.12.1+1. The script warms the selected
runtime path, measures heap after requested GC, fills source and destination
buffers, copies 16 times and grows source by appending it to itself. All
three resulting buffers remain reachable and are checked after GC; total
retained native byte backing is exactly four times the input size.
The boxed baseline calls the retained generic Object[]/Long runtime
with the same byte values and operations, including its overlap temporary.
| Input | Storage | Exact native backing | Heap delta after GC | Copy 16 times | Append once |
|---|---|---|---|---|---|
| 2 MiB | byte[] | 8,388,608 B | 11,052,888 B | 0.96 ms | 2.39 ms |
| 2 MiB | Object[]/Long | — | 61,436,056 B | 234.71 ms | 49.93 ms |
| 8 MiB | byte[] | 33,554,432 B | 36,218,712 B | 17.88 ms | 11.72 ms |
| 8 MiB | Object[]/Long | — | 237,637,872 B | 1,536.15 ms | 228.91 ms |
Array lengths establish exact one-byte element backing; heap deltas include JVM array/object overhead, allocator/GC accounting and measurement noise. Timing and heap observations are illustrative, with no mandatory RSS or heap threshold. Byte-string conversion still copies into immutable Java strings; this is an in-memory runtime, not a streaming API.
Remaining work
Section titled “Remaining work”C# subsequently passed its bounded native byte-storage checks. Python subsequently gained native byte-storage checks. Rust subsequently gained native byte-storage evidence. C subsequently gained native byte-storage evidence. Existing generic non-byte append growth can leave unset spare capacity; this assignment preserves the generic representation and does not repair that separate gap. Pending host I/O, key/buffer completion lifetimes, cancellation, shutdown, library exports and generated SDK packages remain Phase 4/5/6 work. Crypto/HTTP unavailable-feature diagnostics GCE002 and the unsupported library-target diagnostic GCE006 remain in place.