Skip to content

Redesign runtime Msg#1004

Open
emil14 wants to merge 16 commits into
mainfrom
codex/redesign-runtime-message-representation
Open

Redesign runtime Msg#1004
emil14 wants to merge 16 commits into
mainfrom
codex/redesign-runtime-message-representation

Conversation

@emil14

@emil14 emil14 commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

Motivation

  • Replace the previous interface-based message representation with a compact tagged-union to reduce allocations and interface dispatch on hot paths.
  • Centralize equality/JSON/match semantics to ensure correctness while enabling faster comparisons and iteration.
  • Update runtime transport and native funcs to use the new Msg API so hot-path ops (List(), Dict(), struct/union access) avoid unnecessary boxing.
  • Make generated code and compiler templates produce and consume the new representation consistently.

Description

  • Replaced the old Msg interface and concrete *Msg types with a single Msg tagged-union struct and MsgKind, plus constructor helpers like NewBoolMsg, NewIntMsg, NewFloatMsg, NewStringMsg, NewListMsg, NewDictMsg, NewStructMsg, NewUnionMsg and NewUnionMsgNoData (see internal/runtime/message.go).
  • Implemented compact storage for primitives (bits/string) and val any for composite values, and centralized Equal, MarshalJSON, String, and Match logic to operate on the new Msg (see internal/runtime/message.go).
  • Updated runtime APIs and hot-path code to use the new representation and zero-value Msg{} as the sentinel for “no data”, including program receive/send paths, interceptors, match/switch/fan_in handlers and others (see changes in internal/runtime/program.go, internal/runtime/interceptors.go, and internal/runtime/funcs/*).
  • Adjusted compiler backend and templates to emit runtime.Msg{} for empty configs, use NewUnionMsgNoData for tag-only unions, and to call .IsStruct()/.Struct() or .IsUnion()/.Union() instead of previous type assertions (see internal/compiler/backend/golang/backend.go, internal/compiler/backend/golang/tpl.go, and generated exports updates).
  • Added unit tests and micro-benchmarks for message operations to validate correctness and measure improvements (internal/runtime/message_test.go and internal/runtime/message_bench_test.go), and documented the change in AGENTS.md.

Testing

  • Ran make build (invokes builds/tests); the runtime and most packages compiled and tests ran, but CI-local example examples/http_get failed due to external network restrictions (test reports: "Domain forbidden").
  • Ran go test ./...; the majority of unit and e2e packages passed, but examples/http_get failed for the same network restriction, causing an overall test failure.
  • Ran golangci-lint run ./...; this did not complete due to the linter binary being built with an older Go version than the repository target (Go 1.24 vs 1.25).
  • Added benchmarks (BenchmarkMsgListIter, BenchmarkMsgDictLookup, BenchmarkMsgEqualList, BenchmarkMsgStructGet) that exercise iteration, lookup, equality, and struct access and are included in the tree for performance evaluation.

Codex Task

===

Below is a detailed explanation of how the current tagged‑union Msg design works (as implemented in the latest commit), with emphasis on the bits/any tricks, negative number handling, and why reflect/unsafe were not used. I’ll also explicitly tie this back to the ideas in the “Proposal: Optimize Message representation via native Go types #996”. All references are to the current repo state you asked about.

  1. How the new Msg design works (tagged‑union + bits + any)
    1.1. Tagged‑union layout
    The new Msg is a single struct that uses:

a tag (MsgKind) to know what kind of value is stored,

a fixed bits uint64 field for fast inline storage of numeric/boolean values,

a str string field for string payloads, and

a val any field for composite payloads ([]Msg, map[string]Msg, StructMsg, UnionMsg).
This is all defined in internal/runtime/message.go.

The key shape is:

type Msg struct {
kind MsgKind
bits uint64
str string
val any
}
Because kind tells you which field is “live,” no interface dispatch is needed to read a value: the accessor (Int(), Float(), List(), etc.) just checks the tag and reads the expected field. The tag check is in mustKind, which panics on invalid access, keeping correctness intact while staying cheap in the hot path.

1.2. The bits trick
The bits uint64 field stores small primitive payloads compactly:

bool is stored as bits = 1 for true, 0 for false.

int64 is stored by casting to uint64 (so it preserves the bit pattern).

float64 is stored using math.Float64bits / Float64frombits (the exact IEEE‑754 bit pattern is round‑tripped).

Constructors show this explicitly:

NewBoolMsg sets bits = 1 for true.

NewIntMsg sets bits = uint64(n).

NewFloatMsg sets bits = math.Float64bits(n).
Accessors reverse this: Bool() checks bits==1, Int() casts bits back to int64, Float() uses math.Float64frombits.

1.3. The any trick
The val any field is used only for composite values:

List() returns msg.val.([]Msg)

Dict() returns msg.val.(map[string]Msg)

Struct() returns msg.val.(StructMsg)

Union() returns msg.val.(UnionMsg)

This keeps composite values “boxed” in a single Msg value rather than through a hierarchy of interface values. The constructors NewListMsg, NewDictMsg, NewStructMsg, NewUnionMsg store the composite payload in val and set the appropriate MsgKind.

1.4. Centralized equality / JSON / match semantics
Equality and JSON formatting are now centralized in Msg, so the runtime doesn’t bounce through type-specific methods and interface dispatch:

Msg.Equal compares tags and then compares the correct backing storage (bits, str, slices/maps, or struct/union types).

Msg.MarshalJSON delegates to the correct representation (primitives, list, dict, struct, union).

Match handles union match logic using tag + optional data check. The logic is equivalent to the previous behavior but performed on the new union value directly (with the hasData bit).

This centralization also makes it easier to reason about correctness: only one place to adjust equality or JSON formatting.

  1. How negative numbers are stored
    Negative integers are stored through the uint64 cast:

func NewIntMsg(n int64) Msg {
return Msg{kind: MsgKindInt, bits: uint64(n)}
}
func (msg Msg) Int() int64 {
return int64(msg.bits)
}
Go’s integer casts preserve the two’s‑complement bit pattern, so a negative int64 value becomes a uint64 with the same raw bits. When it’s cast back to int64, the original negative number is recovered. This is safe and lossless for all int64 values. The behavior is in NewIntMsg and Int() respectively.

  1. Why no reflect, no unsafe, no runtime package tricks?
    3.1. No reflect
    Using reflect would:

reintroduce dynamic dispatch and allocations (especially for composite iteration and equality),

complicate the hot path with type switches on reflect.Kind, and

make correctness harder to guarantee (e.g., reflect.DeepEqual is slower and has edge cases).

The current design uses explicit tags and typed accessors, which is faster and more predictable. All runtime dispatch is already O(1) through a switch on MsgKind. That’s directly visible in Msg.Equal, Msg.MarshalJSON, and Msg.String.

3.2. No unsafe
unsafe could theoretically pack more data or avoid bounds checks, but it risks undefined behavior across Go versions and architectures. Given Neva’s emphasis on correctness and portability (multi‑arch builds in make build), staying in safe Go is consistent with the runtime’s long‑term stability. The new approach already avoids interface allocations without unsafe tricks.

3.3. “runtime” package tricks
Direct runtime package usage (e.g., runtime.memmove or internal layout assumptions) would introduce:

implementation‑detail coupling to Go’s runtime,

stricter version constraints (breaking with Go upgrades),

unclear portability for different architectures.

Given the performance gains already captured by eliminating interface dispatch in message handling, it’s not necessary at this stage.

3.4. Alternatives that were effectively chosen
The tagged‑union approach is essentially the best low‑level alternative to reflect/unsafe in Go:

explicit tagging,

fixed layout for primitives,

minimal boxing for composites.

This is already implemented in Msg and its accessors. It’s a classic approach in high‑performance Go code when you want predictable hot‑path behavior.

  1. Relationship to “Proposal: Optimize Message representation via native Go types Proposal: Optimize Message representation via native Go types #996
    The issue proposes native composite storage (e.g., []int64, map[string]string, etc.) to avoid []Msg and map[string]Msg overhead. The new tagged‑union design does not yet store native composite slices or maps, but it lays the groundwork for that direction by:

Centralizing composite decoding in Msg.List() / Msg.Dict(), so we can later extend them to detect “native list/map” encodings without changing call sites. The accessors are already centralized and are the sole place to retrieve composites.

Making Msg a flat struct, so future composite variants can be stored in val any with new internal types (e.g., []int64 or map[string]string) and recognized in List() / Dict() or in new helper methods.

Centralizing equality and JSON so that future composite variants can be handled without refactoring all runtime funcs or compiler code. The switch on MsgKind is the single dispatch point to extend.

In other words: the new design doesn’t yet implement the “native composite storage” proposed in #996, but it makes it feasible without another large refactor. The hot‑path methods (List, Dict, Equal, MarshalJSON, Match) are centralized and ready for extension.

What the proposal would still require
To fully implement #996’s idea, you’d need:

additional internal composite variants (e.g., listInt64, listString types),

expanded List()/Dict() to either return []Msg/map[string]Msg or provide “fast path” iterators for native composites,

or introduce new accessors (e.g., ListInt64()), which would be a more invasive API change.

The current design doesn’t do this yet, but it is compatible with the incremental approach suggested in the proposal (e.g., start with []bool|int|string and map[string]bool|int|string and treat them as special composite variants stored in val). The single tagged‑union struct makes that evolution straightforward.

  1. Additional design details you asked about
    5.1. MsgKind and sentinel values
    MsgKindInvalid (tag = 0) is the sentinel “empty” message. Msg.IsValid() simply checks that kind != MsgKindInvalid. This is used in generated code to detect empty results without relying on nil (since Msg is no longer an interface).

5.2. Union “no data” semantics
Unions now track whether they contain data using the hasData bool in UnionMsg. The constructors NewUnionMsg and NewUnionMsgNoData set hasData appropriately. Match logic uses hasData to preserve the pattern‑matching behavior (tag only vs. tag+payload).

5.3. Compiler and generated code changes
The compiler backend now emits:

runtime.Msg{} as the “empty config” sentinel,

runtime.NewUnionMsgNoData for tag-only unions,

typed conversions for list/dict/struct/union fields in getMsgFromGo and getGoFromMsg.
These changes ensure that runtime and generated code align with the new Msg struct representation.

  1. Summary of key benefits (performance rationale)
    Eliminated interface dispatch in hot paths like List(), Dict(), Equal(), MarshalJSON().

Reduced allocations: primitives are stored inline in a single struct; composite containers do not require extra wrapper structs per element.

Tighter cache behavior: Msg is a compact struct with small fixed‑size fields and a single any for composites.

These benefits are particularly important in runtime funcs like list and dict operations (e.g., list iteration, dict lookup). The new benchmarks were specifically added to measure this (BenchmarkMsgListIter, BenchmarkMsgDictLookup, etc.).

===

Below is a detailed, source‑anchored explanation of why bits uint64 is used for booleans, what alternatives exist, and why strings remain string rather than []byte in the current design. I’m not modifying any code per your instruction.

  1. Why booleans use a 64‑bit field (vs 8‑bit or bit‑packing)
    1.1. The actual layout goal: fast, fixed‑shape Msg
    The current Msg struct is designed to be a fixed‑shape tagged union with minimal branching, so all payloads (bool/int/float) fit in the same inline slot (bits uint64). This eliminates per‑type allocations and avoids interface dispatch in hot paths. The struct uses one tag plus fixed payload fields: bits uint64, str string, and val any.

1.2. Why bits is always 64‑bit
Using a single 64‑bit slot for all numerics and bool yields:

No extra switching or per‑type storage fields inside Msg (we don’t need bool boolVal, int64 intVal, float64 floatVal, etc.).

Predictable layout across platforms and versions.

Fast accessors: Bool(), Int(), and Float() just reinterpret bits based on the MsgKind.
This behavior is encoded in constructors and accessors:
NewBoolMsg sets bits = 1 if true, 0 otherwise; Bool() reads bits == 1; NewIntMsg casts to uint64, Int() casts back; NewFloatMsg uses Float64bits/Float64frombits.

1.3. “But bool only needs 1 byte”
That is true at the value level, but struct layout and access cost matter more here than absolute byte size:

The struct already contains str string (two machine words) and val any (an interface header). The incremental memory difference between a 1‑byte bool and an 8‑byte uint64 is negligible in the overall Msg footprint.

If we used a smaller bool field and a separate field for int/float, we’d either add more branching or grow the struct with multiple typed fields, which can increase size and reduce cache locality.

A single uint64 keeps the code path uniform and avoids extra branching in hot paths like Equal, String, or MarshalJSON which all use the bits slot directly based on MsgKind.

1.4. Could we bit‑pack booleans more tightly?
Technically yes, but the cost would be:

introducing bit masking/shift ops and/or

extra fields or special‑case code paths
That would add complexity and risk branch mispredicts in the hot path. The current design optimizes for predictability and minimal dispatch, not maximum byte‑level compression.

  1. Why string is stored as string and not []byte
    2.1. Strings already have efficient representation in Go
    A string in Go is a read‑only header (pointer + length) and is immutable. That immutability allows:

safe sharing without copies,

efficient hashing and equality, and

direct JSON encoding without conversions.

The Msg type uses the str string field for string values, and MarshalJSON directly calls json.Marshal(msg.Str()) — no extra conversions needed.

2.2. Using []byte would increase complexity
If strings were stored as []byte, you’d need to:

clone or freeze slices to preserve immutability (to avoid aliasing bugs),

convert []byte to string for JSON or for any string comparison, which allocates unless you use unsafe,

and ensure that code calling Str() doesn’t accidentally mutate internal buffers.

Since many runtime funcs and interceptors treat strings as immutable values (e.g., formatting in interceptors, string concatenations, comparisons), string is the safer, zero‑copy type for “message” semantics. The debug interceptor, for example, formats string messages directly using msg.Str() with quoting; that would require additional conversions if []byte were used internally.

2.3. Would []byte help performance?
Usually, not in this context. The runtime message layer is not doing large byte‑level operations on strings; it primarily passes them through. The more common operations in the runtime are:

equality (Msg.Equal),

JSON serialization,

matching and routing.
These are better served by string. If we introduced []byte, we’d likely pay more conversion costs than we’d save.

  1. Alternatives considered and tradeoffs (summary)
    Option Benefits Downsides
    Single uint64 bits for bool/int/float Fast, uniform, minimal branching “Wastes” bytes for bool, but overall Msg size dominated by other fields
    Separate bool field Saves bytes for bool Adds fields/branches, expands struct or complicates accessors
    Bit‑packing multiple booleans Best memory density Adds complexity and potentially hurts hot‑path CPU performance
    string storage Immutable, zero‑copy, cheap equality None significant for this use case
    []byte storage Potentially mutable Requires conversions or unsafe; higher complexity/risk
  2. Direct answer to your concern (in one paragraph)
    Yes, booleans are stored in a 64‑bit slot even though they need fewer bits, but the design optimizes for uniform access and minimal branching, and the incremental memory is negligible compared to the string and any fields already present in Msg. The choice avoids multiple payload fields or bit‑packing complexity and keeps Equal/MarshalJSON/String fast and predictable. Similarly, strings are stored as string because it is immutable, efficient for equality/JSON, and avoids conversions; moving to []byte would likely increase allocations or require unsafe without meaningful performance wins in the runtime’s current workloads.

Comment thread AGENTS.md Outdated
Comment thread internal/runtime/funcs/switch.go Outdated
Comment thread internal/runtime/funcs/match.go Outdated
Comment thread internal/runtime/funcs/fan_in.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go
Comment thread internal/runtime/message.go
Comment thread internal/runtime/message.go
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message.go Outdated
Comment thread internal/runtime/message_bench_test.go Outdated
@emil14 emil14 changed the title Redesign runtime Msg into a high-performance tagged-union and update runtime/funcs/compiler to use it Redesign runtime Msg Feb 14, 2026
@emil14

emil14 commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up: I split out JSON-format correctness fixes into #1030 (safe spacing outside strings + union data JSON quoting/escaping) so this can merge on NAME:
neva - Dataflow programming language with static types and implicit parallelism

USAGE:
neva [global options] command [command options]

COMMANDS:
version Get current Nevalang version
upgrade Upgrade to newest Nevalang version
new Create new neva project
get Add dependency to current module
run Build and run neva program from source code
build Build neva program
osarch List supported OS/architecture combinations for native target
doc Search the standard library for symbols using regular expressions
help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
--help, -h show help without waiting for message-representation redesign.\n\nAfter rebasing #1004, please port/cherry-pick equivalent changes from #1030 to preserve the same formatting guarantees in the new implementation.

@emil14

emil14 commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up: I split out JSON-format correctness fixes into #1030 (safe spacing outside strings plus union data JSON quoting and escaping) so this can merge on main without waiting for message-representation redesign.\n\nAfter rebasing #1004, please port or cherry-pick equivalent changes from #1030 to preserve the same formatting guarantees in the redesigned message implementation.

Comment thread internal/runtime/funcs/http.go Outdated
Comment thread internal/runtime/message.go Outdated
@emil14

emil14 commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator Author

Housekeeping link for the runtime-perf track:

  • #1023 is the benchmark baseline PR this redesign should be compared against.
  • #1015 is the broader perf-observability issue collecting the runtime benchmark/profiling direction.
  • #1088 now tracks the follow-up question about how Neva should split unit vs e2e vs benchmark coverage for stdlib components and Go-backed runtime funcs.

@github-actions

Copy link
Copy Markdown

Based on my thorough analysis of this pull request, here is my review:


Review Summary: Redesign runtime Msg

Critical Issues (Blocking)

1. JSON Spacing Normalization Still Unsafe ⚠️

The addJSONSpaces function (message.go:602-638) uses a character-by-character approach that is still potentially problematic for complex string payloads. While it handles escaped characters correctly, this approach is fundamentally fragile. The PR mentions #1030 split out JSON fixes, but these haven't been incorporated here. Recommendation: Port the "only add spaces outside quoted strings" fix from #1030 before merging.

2. Missing E2E Benchmark ⚠️

Per the review comments (2026-02-14), an E2E benchmark should be added in a separate PR first, then this branch rebased. The current message_bench_test.go only has unit-level benchmarks. The reviewer explicitly stated this is a CRITICAL BLOCKER.

Design Issues

3. Struct MarshalJSON Allocates Unnecessarily

In StructMsg.MarshalJSON() (lines 341-363), the code creates a map[string]Msg just to marshal it to JSON. This is inefficient for large structs. Consider using strings.Builder directly or a JSON encoder.

4. HTTP Body Handling

The http.go change uses NewBytesMsg(body) which aligns with the bytes type. However, ensure this is compatible with the current main branch's std/http.Response.body type (review comment 2026-03-22 raised this concern).

Positive Aspects

  1. Clean Tagged-Union Design: The new Msg struct with kind, bits, str, and val fields is elegant and cache-friendly.

  2. Zero-Allocation Benchmarks: The unit benchmarks show excellent performance:

    • BenchmarkMsgListIter: 0 allocs/op
    • BenchmarkMsgDictLookup: 0 allocs/op
    • BenchmarkMsgEqualList: 0 allocs/op
    • BenchmarkMsgStructGet: 0 allocs/op
  3. Fail-Fast Behavior: Invalid message operations now panic instead of silently returning invalid data, which is appropriate for internal invariant violations.

  4. Comprehensive Test Coverage: Tests cover negative integer round-tripping, struct equality, union matching, JSON marshaling with special characters, and invalid message panics.

  5. Negative Integer Handling: Correctly uses two's-complement bit pattern storage in uint64 with proper round-trip testing.

  6. Code Generation Alignment: The compiler backend and templates properly use runtime.Msg{}, NewUnionMsgNoData, and the new accessor methods.

Minor Suggestions

  1. Documentation: Consider adding a package-level comment explaining the Msg design and the bits/val storage strategy for future maintainers.

  2. Struct JSON Optimization: The StructMsg.MarshalJSON could use a strings.Builder approach instead of allocating a map, though this could be deferred to a follow-up optimization PR.

Conclusion

This is a well-designed refactoring that improves performance through a compact tagged-union representation. The code is clean, tests pass, and benchmarks show zero allocations for hot paths.

However, this PR should not merge until:

  1. The E2E benchmark PR is created and merged first (per review requirements)
  2. The JSON spacing normalization safety concerns from Fix runtime JSON spacing corruption in message formatting #1030 are addressed

Once these blockers are resolved, this PR will be ready for merge. The core design is solid and represents a significant improvement over the interface-based approach.

New%20session%20-%202026-04-12T12%3A43%3A41.297Z
opencode session  |  github run

@emil14

emil14 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator Author

Replying to @github-actions opencode review (2026-04-12)

"JSON spacing normalization still unsafe"

Handled: removed addJSONSpaces and replaced with direct JSON emission paths in runtime message serialization (f9cee287, f08d9ef3).

"Missing E2E benchmark"

Confirmed already in place earlier in this PR track; local validation also included benchmark execution (go test -run ^ -bench ...).

"HTTP body handling"

Verified current behavior matches main: std/http.Response.body is bytes and runtime path sends NewBytesMsg(body).

…e-message-representation

# Conflicts:
#	go.sum
@github-actions

Copy link
Copy Markdown

PR Review: Redesign runtime Msg

After reviewing the code changes, existing review comments, and implementation details, here's my assessment:

Summary

This PR replaces the interface-based message representation with a compact tagged-union struct (Msg with kind, bits, str, val fields). The design is sound and addresses performance concerns through reduced allocations and eliminated interface dispatch on hot paths.

Critical Items - Status Check

Item Status Notes
JSON serialization safety ADDRESSED addJSONSpaces removed in commits f9cee287 + f08d9ef3; now uses direct JSON writers with context-aware marshalNestedJSON
E2E benchmark VERIFIED benchmarks/bench_test.go has BenchmarkRuntimeE2E; infrastructure exists
HTTP body handling CORRECT Uses NewBytesMsg(body) matching std/http.Response.body bytes type
Negative integer storage CORRECT Two's-complement bit pattern in uint64 round-trips correctly

Design Highlights

1. Tagged-Union Layout (message.go:38-45)

type Msg struct {
    val  any          // composites: []Msg, map[string]Msg, StructMsg, UnionMsg
    str  string       // string payloads
    bits uint64       // bool(0/1), int64(bit pattern), float64(IEEE-754 bits)
    kind MsgKind      // tag for dispatch
}
  • Clean separation: primitives inline in bits, composites boxed in val
  • Zero-value Msg{} is MsgKindInvalid sentinel

2. Fail-Fast Semantics
Accessors panic on kind mismatch (e.g., calling Int() on a String message). This is appropriate for internal invariant violations per the AGENTS.md policy.

3. JSON Serialization (message.go:582-693)
The current implementation uses direct byte-slice building with marshalMapWithSpaces, marshalStructFieldsWithSpaces, and marshalNestedJSON - no unsafe post-processing on serialized strings.

4. Union Representation (message.go:422-427)

type UnionMsg struct {
    tag     string
    data    Msg
    hasData bool
}

String tags are kept for readability/debugging; indexed tags would require compiler/runtime coordination (noted as future optimization).

Test Coverage

The tests in message_test.go cover:

  • Struct equality ignoring field order
  • Union matching (with/without data)
  • Dict/struct JSON marshaling with special characters
  • Negative integer round-trip (TestIntMsgNegativeRoundTrip)
  • Invalid message panics (TestInvalidMsgPanics)
  • Escaped string preservation in nested structures

Benchmarks show zero allocations for hot paths:

  • BenchmarkMsgListIter: 0 allocs/op
  • BenchmarkMsgDictLookup: 0 allocs/op
  • BenchmarkMsgEqualList: 0 allocs/op
  • BenchmarkMsgStructGet: 0 allocs/op

Minor Observations

  1. benchmarks/message_passing/ directory doesn't exist in the current branch, though referenced in PR comments as commit 3a7d736b. If this benchmark is required for comparison, ensure it's either:

    • Already merged to main (the E2E benchmark infrastructure is there)
    • In a separate PR that should precede this one
  2. HTTP body is bytes - The change in http.go:71 correctly uses NewBytesMsg(body), preserving the bytes type for transport.

  3. Compiler alignment - Backend and templates properly use runtime.Msg{}, NewUnionMsgNoData, and the new accessor methods (.IsStruct(), .Struct(), etc.).

Conclusion

This PR is ready to merge. The core design is solid, previous blocking concerns have been addressed, and the implementation maintains correctness while improving performance. The tagged-union approach successfully eliminates interface dispatch overhead without resorting to unsafe or reflect.

New%20session%20-%202026-04-12T16%3A41%3A16.586Z
opencode session  |  github run

@emil14

emil14 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

Finalization update for #1004 (head 670fbfe).

What was done

  • Merged origin/main into codex/redesign-runtime-message-representation and resolved conflicts in:
    • internal/runtime/message.go
    • internal/runtime/program.go
    • internal/runtime/funcs/switch.go
    • internal/compiler/utils/generated/exports.go
  • Regenerated compiler glue via repo CLI (not global binary):
    • go run ./cmd/neva build --target=go --target-go-mode=pkg --target-go-runtime-path=../runtime --output=utils/generated utils (from internal/compiler)
  • Kept tagged-union Msg design and aligned merge resolution around it.
  • Resolved all previously open review threads in this PR.

Validation

  • Local targeted checks:
    • go test ./internal/runtime/...
    • go test ./internal/compiler/...
    • golangci-lint on runtime + go backend paths (clean)
  • GitHub checks on current head: all required checks are green (gofix-check, golangci, govulncheck, unit_tests, e2e_tests).

Benchmark proof (agreed protocol)

Baseline: origin/main

Commands used:

  • go test ./benchmarks -run '^$' -bench '^BenchmarkRuntimeE2E$' -benchmem -count=3
  • go test ./internal/runtime -run '^$' -bench '^BenchmarkMsg' -benchmem -count=5
  • benchstat over raw outputs

Results:

  1. Runtime E2E (main vs this branch)

    • benchstat geomean: -8.33% sec/op (branch faster on geomean)
    • B/op geomean: +0.21% (effectively flat)
    • allocs/op geomean: no change
    • Note: with count=3, most per-subbenchmark rows are not statistically significant at 95% (benchstat warns about sample size), but there is no signal of broad regressions in allocs/memory and geomean runtime trends positive.
  2. Msg microbench (BenchmarkMsg*)

    • main currently has no matching BenchmarkMsg*, so cross-branch delta cannot be computed directly for that suite.
    • Current branch absolute results (count=5):
      • BenchmarkMsgListIter: ~4.39 µs/op, 0 allocs
      • BenchmarkMsgDictLookup: ~17.9 µs/op, 0 allocs
      • BenchmarkMsgEqualList: ~2.78 µs/op, 0 allocs
      • BenchmarkMsgStructGet: ~74.65 ns/op, 0 allocs

Given merge resolution, green CI, addressed review threads, and benchmark evidence above, this PR is in ready-to-merge state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant