Skip to content

Conversation

@FlameyosSnowy
Copy link
Contributor

@FlameyosSnowy FlameyosSnowy commented Sep 4, 2025

Stabilize intrinsics and compiler so that compile-time errors are gone and we can focus on testing

Summary by CodeRabbit

  • New Features
    • Added core intrinsics (sizeof/alignof, pointer ops, stack allocation, syscall).
    • Introduced stdlib modules: memory, pointer, fixed-size stack arrays, maths, ranges.
    • Added OS interfaces for filesystem, networking, and time, with Linux x86_64 implementations.
  • Refactor
    • Redesigned pointer and array APIs; grammar expanded with generics, interfaces, type aliases, and optional chaining.
    • Converted some structs to type aliases (e.g., Vec types).
  • Removals
    • Removed legacy backend and fiber runtime; dropped null-pointer and array intrinsics.
  • Chores
    • Updated dependencies (fxhash, smallvec, parking_lot, etc.); removed legacy hasher.

@FlameyosSnowy FlameyosSnowy added the bug Something isn't working label Sep 4, 2025
@FlameyosSnowy FlameyosSnowy marked this pull request as draft September 4, 2025 13:00
@FlameyosSnowy
Copy link
Contributor Author

Testing the discord webhook :D

@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

Walkthrough

Multiple subsystems were reworked. The backend module and its grammar were removed. MIR expression lowering switched to interned StrId keys with a shared StringPool. Cranelift now imports clif_type. IR moved from SeaHash to FxHash. Zeta gained new intrinsic, memory, pointer, array, math, OS syscall, FS, net, and time modules; parser grammar expanded significantly; a fiber runtime was removed.

Changes

Cohort / File(s) Summary
Dependency updates
emberforge-compiler/Cargo.toml, ir/Cargo.toml, zetaruntime/Cargo.toml
Added smallvec/arrayvec/parking_lot/num_cpus/rstest; replaced seahash with fxhash; removed some runtime deps; reordered dashmap.
Backend removal
emberforge-compiler/src/backend/grammar.pest, emberforge-compiler/src/backend/mod.rs
Deleted backend grammar and module, removing public backend exports.
MIR interning refactor
emberforge-compiler/src/midend/ir/expr_lowerer.rs
Migrated symbol maps from String+SeaHash to StrId+FxHash; added StringPool; updated field/method/interface dispatch and constructors; public fields/signatures changed accordingly.
Cranelift refactor
engraver-assembly-emit/src/cranelift/cranelift_backend.rs, engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs
Imported shared clif_type; removed local helper; replaced unimplemented! with todo!; substantial intrinsic/type/layout/syscall scaffolding; removed PtrNull/PtrIsNull and array intrinsics; added Layout/TargetInfo APIs.
IR hasher swap
ir/src/sea_hasher.rs
Removed SeaHashBuilder and its BuildHasher impl.
Zeta intrinsics and core lang
lib/zeta/intrinsics.zeta, lib/zeta/lang/mem.zeta, lib/zeta/lang/pointer.zeta, lib/zeta/lang/arrays.zeta, lib/zeta/lang/maths.zeta
Added intrinsic APIs (sizeof/alignof, pointer and stack ops); added mem helpers; reworked Ptr API to free functions; redesigned Array as fixed-length pointer-backed type with bounds checks; added math constants and basic functions.
Zeta OS syscall surface
lib/zeta/os_intrinsics.zeta
Declared extern syscall0–syscall6 intrinsics.
Zeta OS async/threading
lib/zeta/os/async/threads.zeta, lib/zeta/os/async/linux.zeta
Added OsThreadSystem interface and LinuxThreads implementation with clone/futex/epoll/io_uring and atomic wrappers.
Zeta filesystem
lib/zeta/os/fs/filesystem.zeta, lib/zeta/os/fs/linux.zeta
Added FileSystem interface and LinuxFilesystemX86_64 implementation (sync, vectored, mmap, async/io_uring).
Zeta networking
lib/zeta/os/net/network.zeta, lib/zeta/os/net/linux.zeta
Added Network interface and LinuxNetworkX86_64 primitives (sockets, epoll, TUN, helpers); introduced net structs.
Zeta time
lib/zeta/os/time/time_system.zeta, lib/zeta/os/time/linux_time_system.zeta
Added TimeSystem interface and LinuxTimeSystem (clock/time/nanosleep).
Parser grammar overhaul
scribe-parser/src/grammar.pest
Major grammar redesign: generics, interfaces, type/alias, visibility, extern blocks, updated expressions/types, optional chaining, packaging.
Runtime removal
zetaruntime/src/fiber.rs
Deleted fiber pool, policies, scheduler, and TLS machinery.
Examples/types
input.zeta
Switched Vec3f/Vec9f from struct to type declarations.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Z as Zeta Code
  participant LF as LinuxFilesystemX86_64
  participant LN as LinuxNetworkX86_64
  participant LT as LinuxTimeSystem
  participant OSI as os_intrinsics::syscallN
  participant K as Kernel

  rect rgba(230,240,255,0.5)
  note over Z,LF: Filesystem operation
  Z->>LF: read(fd, buf, len)
  LF->>OSI: syscall3(SYS_READ, fd, buf.addr(), len)
  OSI->>K: invoke
  K-->>OSI: isize
  OSI-->>LF: isize
  LF-->>Z: isize
  end

  rect rgba(230,255,230,0.5)
  note over Z,LN: Networking operation
  Z->>LN: socket(domain, type, proto)
  LN->>OSI: syscall3(SYS_SOCKET, domain, type, proto)
  OSI->>K: invoke
  K-->>OSI: fd/isize
  OSI-->>LN: isize
  LN-->>Z: isize
  end

  rect rgba(255,245,230,0.5)
  note over Z,LT: Time operation
  Z->>LT: clockGetMonotonic(ts)
  LT->>OSI: syscall2(SYS_CLOCK_GETTIME, CLOCK_MONOTONIC, ts.addr())
  OSI->>K: invoke
  K-->>OSI: isize
  OSI-->>LT: isize
  LT-->>Z: isize
  end
Loading
sequenceDiagram
  autonumber
  participant L as MirExprLowerer
  participant SP as StringPool
  participant CMS as class_method_slots
  participant CMM as class_mangled_map
  participant EM as emit_call

  note over L,SP: Interned symbol resolution
  L->>SP: intern(ident) / resolve(StrId)
  SP-->>L: StrId / &str

  alt Direct method slot
    L->>CMS: lookup(class_id, method_id)
    CMS-->>L: Some(slot)
    L->>EM: ClassCall(object, slot, args)
  else Mangled free function
    L->>CMM: lookup(class_id, method_id)
    CMM-->>L: Some(fn_id)
    L->>EM: Call(fn_id, args)
  else Fallback
    L->>EM: Call(scoped_name, args)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120–180 minutes

Poem

In burrows of code, I twitch my nose,
New syscalls bloom where the pointer goes.
Arrays get bounds, the hashes turn swift,
Grammars grow types—a generics gift.
Backends retire, fibers take leave—
I thump the ground: ship it, believe! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary intent of the changeset: stabilizing intrinsics and the compiler to prepare for tests. It is a single clear sentence that relates directly to the large number of intrinsic and compiler refactors in the diff. The phrasing is concise, specific, and suitable for a teammate scanning the project history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests

✅ Unit Test PR creation complete.

  • Create PR with unit tests
  • Commit unit tests in branch intrinsic-functions-structs
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

@FlameyosSnowy FlameyosSnowy marked this pull request as ready for review September 16, 2025 10:50
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 27

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
emberforge-compiler/src/midend/ir/expr_lowerer.rs (1)

190-201: Wrong SSA result type for comparisons.

Binary ops are always marked I64; comparisons should yield Bool.

Apply:

     let l = self.lower_expr(left);
     let r = self.lower_expr(right);
     let v = self.current_block_data.fresh_value();
+    let result_ty = match op {
+        Operator::Equals
+        | Operator::NotEquals
+        | Operator::LessThan
+        | Operator::LessThanOrEqual
+        | Operator::GreaterThan
+        | Operator::GreaterThanOrEqual => SsaType::Bool,
+        _ => SsaType::I64,
+    };
     self.emit(Instruction::Binary {
         dest: v,
         op: lower_operator_bin(op),
         left: Operand::Value(l),
         right: Operand::Value(r)
     });
-    self.current_block_data.value_types.insert(v, SsaType::I64);
+    self.current_block_data.value_types.insert(v, result_ty);
engraver-assembly-emit/src/cranelift/cranelift_backend.rs (2)

193-201: Don’t fabricate zero values for missing Phi incomings.

Silently passing 0 masks IR bugs and can corrupt state. Prefer explicit failure (or a debug assertion) so invalid IR doesn’t proceed.

-                            // invalid IR; use zero to avoid panic
-                            let z = builder.ins().iconst(types::I64, 0);
-                            args.push(BlockArg::Value(z));
+                            // invalid IR; fail fast
+                            panic!("Phi incoming from predecessor {:?} missing for dest {:?}", curr_bb, dest);

Repeat the same change for both then/else paths.

Also applies to: 231-236, 254-260


451-454: Avoid emitting a return for Void.

Cranelift signatures shouldn’t include a return when the function returns void.

-            sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+            if !matches!(func.ret_type, SsaType::Void) {
+                sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+            }
@@
-        sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+        if !matches!(func.ret_type, SsaType::Void) {
+            sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+        }
@@
-        sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+        if !matches!(func.ret_type, SsaType::Void) {
+            sig.returns.push(AbiParam::new(clif_type(&func.ret_type)));
+        }

Also applies to: 468-471, 613-616

engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (3)

30-41: Stray closing brace before block comment breaks parsing.

There’s an extra } before /* ... */.

-}/*
+/* 

131-152: Fix U128 mapping — avoid truncation

HirType::U128 is mapped to SsaType::U64 (silent truncation 128→64). Do not keep this.

  • Preferred: add SsaType::U128, update layout_of (size: 16, align: 16) and any CLIF/type maps, then map HirType::U128 => SsaType::U128.
  • Minimal (temporary): map HirType::U128 => SsaType::I128 and add a clear comment/TODO about the sign mismatch and that a U128 variant should be added.

Location: engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs — clif_type_of (around lines 131–152). If adding U128 also update layout_of in the same file.

Diff (minimal temporary change):

-        HirType::U128 => SsaType::U64,
+        HirType::U128 => SsaType::I128, // preserve width; replace with SsaType::U128 + layout later

297-324: Normalize CLIF boolean in PtrCastToRaw — return a consistent integer boolean (I64)

File: engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (PtrCastToRaw). icmp returns a b1 but the else branch builds an I8 const; produce a canonical b1 false and bint it to the repo's integer-boolean (types::I64) before returning.

-            let nonzero_test = if val_ty == ptr_ty {
+            let nonzero_b1 = if val_ty == ptr_ty {
                 let zero = builder.ins().iconst(ptr_ty, 0);
                 builder.ins().icmp(IntCC::NotEqual, val, zero)
             } else if val_ty.is_int() {
                 // widen/truncate to pointer width then compare
                 let normalized = ensure_value_is_ptr_width(builder, val, ptr_ty, val_ty);
                 let zero = builder.ins().iconst(ptr_ty, 0);
                 builder.ins().icmp(IntCC::NotEqual, normalized, zero)
             } else {
-                // Not a pointer-ish or integer-ish typed SSA value: produce constant false.
-                // Make sure to return a boolean-typed value consistent with your convention.
-                let zero_bool = builder.ins().iconst(types::I8, 0); // boolean false
-                return Some(zero_bool);
+                // Not pointer/int-like: produce canonical `b1` false by comparing 0 == 0.
+                let z = builder.ins().iconst(ptr_ty, 0);
+                builder.ins().icmp(IntCC::Equal, z, z)
             };
 
-            // `nonzero_test` is an i1/icmp predicate represented as an int by CLIF.
-            // Return it directly (we used I8 earlier as our boolean conv).
-            Some(nonzero_test)
+            // Convert `b1` predicate into the repository's integer-boolean (I64).
+            Some(builder.ins().bint(types::I64, nonzero_b1))

Verify boolean convention across loads/stores/returns (cranelift_backend.rs uses types::I64 for booleans).

🧹 Nitpick comments (42)
input.zeta (1)

17-31: Vec9f intent and naming: minor clarity nits

  • The “f” suffix suggests float, but all components are i32. Consider Vec3i/Vec9i or switching fields to f32 if floats were intended.
  • multiply(Vec3f by) mutates in place. If you expect functional style too, consider adding a non‑mutating variant that returns a new value.
scribe-parser/src/grammar.pest (9)

88-89: Duplicate/competing visibility rules.

visibility_modifier and visibility/public/private/protected duplicate concepts and diverge (“protected” exists only in the latter). Keep one source of truth.

-visibility = _{ public_keyword | private_keyword | protected_keyword }
-public_keyword = { "public" }
-private_keyword = { "private" }
-protected_keyword = { "protected" }
+// Remove duplicate visibility rules or align visibility_modifier to include protected if supported:
+// visibility_modifier = { "public" | "private" | "protected" | "package" | "module" }

Also applies to: 109-113


136-137: Shift operators inconsistent; operator_shift is unused.

Either remove operator_shift or include all operators in shift for consistency.

-shift     = { term ~ (("<<" | ">>") ~ term)* }
+shift     = { term ~ (("<<" | ">>" | "<<<" | ">>>") ~ term)* }
@@
-operator_shift = { ">>" | "<<" | ">>>" | "<<<" }
+// operator_shift no longer needed; remove if unused elsewhere.

Also applies to: 175-176


161-165: error_prefix is a suffix; naming/placement is misleading and may block intended syntax.

If the language uses “!T” (prefix) and “T?” (nullable suffix), adjust accordingly; if “T!” is intended, rename to error_suffix.

-var_type = { (lambda_type | basic_type) ~ error_prefix? ~ nullable_suffix? }
-error_prefix = { "!" }
+var_type = { error_prefix? ~ (lambda_type | basic_type) ~ nullable_suffix? }
+error_prefix = { "!" }

Or, if “T!” is correct, rename:

-error_prefix = { "!" }
+error_suffix = { "!" }
-var_type = { (lambda_type | basic_type) ~ error_prefix? ~ nullable_suffix? }
+var_type = { (lambda_type | basic_type) ~ error_suffix? ~ nullable_suffix? }

61-61: Dead/duplicate rules: stmt_ending and comparison_expr appear unused.

Remove to simplify the grammar and reduce maintenance cost.

-stmt_ending    = _{ "\n" | WHITESPACE* }
@@
-comparison_expr = { expr ~ comparison_op ~ expr }

Also applies to: 179-179


75-79: fun_decl modifiers ordering: be explicit about extern-only placement and duplication.

You allow (extern_modifier ~ string)? and unsafe_modifier? in the same cluster; confirm intended combinations and that extern strings can’t appear twice. Consider a dedicated rule for function modifiers with a deterministic order.

If ordering matters, refactor to:

-fun_decl = {
-    visibility_modifier? ~ unsafe_modifier? ~ (extern_modifier ~ string)? ~
-    mut_keyword? ~ static_modifier? ~ (inline_keyword | noinline_keyword)? ~
-    (type_expr ~ ".")? ~ ident ~ generic_params? ~ param_list ~ function_type_annotation? ~ (block | arrow_expr | SEMI?)
-}
+fun_decl = {
+    visibility_modifier? ~ unsafe_modifier? ~ extern_spec? ~ storage_spec? ~ inlining_spec? ~
+    (type_expr ~ ".")? ~ ident ~ generic_params? ~ param_list ~ function_type_annotation? ~ (block | arrow_expr | SEMI?)
+}
+extern_spec = { (extern_modifier ~ string) }?
+storage_spec = { (mut_keyword? ~ static_modifier?) }
+inlining_spec = { (inline_keyword | noinline_keyword)? }

33-36: generic_param: constraints list grammar is ambiguous (“: A, B, C” attaches to the same param?).

If constraints are a list, wrap them as a group; otherwise, this can read as multiple generic_params.

-generic_param = { const_keyword? ~ ident ~ (":" ~ ident ~ ("," ~ ident)*)? }
+generic_param = { const_keyword? ~ ident ~ (":" ~ ident ~ ("," ~ ident)*)? }
+// Consider: ":" ~ ident_list where ident_list = { ident ~ ("," ~ ident)* }

52-55: package_stmt placement and let short form.

package_stmt is a top-level directive; ensure it’s accepted before other stmts (consider SOI anchored form). Also, shorthand_let_stmt is defined but wasn’t used until wired in earlier.

Optional stricter top-level:

-program =  { SOI ~ stmt* ~ EOI }
+program =  { SOI ~ package_stmt? ~ stmt* ~ EOI }

148-156: Compound assignment overlaps with assign_op; ensure precedence is intended.

assign_op includes “=” or compound_assign_op. Keep compound_assign_op separate to avoid mis-parsing “=”.

-assign_op = { "=" | compound_assign_op }
+assign_op = { "=" | "+=" | "-=" | "*=" | "/=" | "%=" | ">>=" | "<<=" | "&=" | "|=" | "^=" }

99-101: match arms require block; consider allowing arrow_expr for terser matches.

If desired, add an arrow form to match_arm.

-match_arm = { pattern ~ "=>" ~ block }
+match_arm = { pattern ~ "=>" ~ (block | arrow_expr) }
emberforge-compiler/src/midend/ir/expr_lowerer.rs (2)

396-432: Remove unused variable in interface upcast.

inner_value_name is computed but unused; drop it (or use get_type if a check is intended).

Apply:

-        let interface_val = match self.current_block_data.value_types.get(&obj_val).cloned() {
-            Some(SsaType::User(ref name, args)) => {
-                let Some(inner_value_name) = self.string_pool.resolve_string(&*name).split("_").last() else { panic!() };
+        let interface_val = match self.current_block_data.value_types.get(&obj_val).cloned() {
+            Some(SsaType::User(_name, args)) => {
                 let upcast_dest = self.current_block_data.fresh_value();
                 self.emit(Instruction::UpcastToInterface {
                     dest: upcast_dest,
                     object: obj_val,
                     interface_id: iface_id
                 });
                 self.current_block_data.value_types.insert(upcast_dest, SsaType::User(interface, args));
                 upcast_dest
             },
             _ => obj_val,
         };

228-244: Prefer slices over &Vec for APIs.

Takes &Vec<Value> where &[Value] is more idiomatic and flexible. Call sites already pass &args, which will coerce to a slice.

Apply:

-    fn init_class_fields_from_args(&mut self, obj: Value, class_name: StrId, args: &Vec<Value>) {
+    fn init_class_fields_from_args(&mut self, obj: Value, class_name: StrId, args: &[Value]) {
lib/zeta/os/async/threads.zeta (3)

3-18: Move Linux syscall numbers out of a cross‑platform interface.

These constants are Linux x86_64‑specific. Keeping them in a generic os.async surface leaks platform details and risks misuse on other arches (aarch64 differs). Relocate to the Linux backend (e.g., os/async/linux.zeta) or gate behind a platform cfg.

-const SYS_CLONE = 56
-const SYS_FORK = 57
-...
-const SYS_EPOLL_PWAIT = 281
+// (moved to zeta.os.async.linux; keep the interface here platform-neutral)

20-59: Separate atomics from OS thread/process APIs.

Mixing user‑land atomics with process/thread/syscall APIs in one interface reduces cohesion. Consider a dedicated AtomicsI32 interface (or a generic Atomics<T>) and keep OsThreadSystem focused on OS calls.


55-58: Return old value for CAS to match common semantics.

atomicCompareExchangeI32(...)-> boolean loses the observed value; most users need the old value whether or not the exchange succeeded. Consider returning the old i32 (and document success criterion) or a struct { success: boolean, old: i32 }.

-atomicCompareExchangeI32(addr: Ptr<i32>, old: i32, new: i32) -> boolean;
+// Option A:
+atomicCompareExchangeI32(addr: Ptr<i32>, expected: i32, desired: i32) -> i32;
+// Option B:
+type CasResultI32(boolean success, i32 old);
+atomicCompareExchangeI32(addr: Ptr<i32>, expected: i32, desired: i32) -> CasResultI32;
lib/zeta/os_intrinsics.zeta (1)

5-28: Keep signature style consistent across all syscallN decls.

All decls should use the same “type name” ordering. After fixing Line 3, the set will be consistent.

lib/zeta/lang/ranges.zeta (2)

13-15: Unify operand widths in contains.

Range<i32> compared against an i64 value invites silent widening. Prefer i32 value or promote the range to i64.

-contains(Range<i32> range, i64 value) -> boolean {
+contains(Range<i32> range, i32 value) -> boolean {

17-19: Clarify length semantics (negative ranges).

end - start can be negative if callers pass start > end. Either enforce start <= end at construction or define whether negative lengths are allowed.

-length(Range<i32> range) -> i64 {
-    range.end - range.start
-}
+length(Range<i32> range) -> i64 {
+    let d = (range.end as i64) - (range.start as i64)
+    if d < 0 { 0 } else { d }
+}
lib/zeta/os/time/time_system.zeta (1)

6-12: Prefer i32 clockId to match Linux clockid_t; document return convention.

Use i32 for clockId. Also document whether negative returns are -errno to guide callers.

-    clockGetTime(usize clockId, Ptr<TimeSpec> ts) -> isize;
+    clockGetTime(i32 clockId, Ptr<TimeSpec> ts) -> isize;
lib/zeta/intrinsics.zeta (1)

13-15: Keep declaration style consistent (optional).

Consider using the same “module extern …” style for stack_alloc[_zeroed] for readability.

-module extern "intrinsic" fn stack_alloc<T, const N: usize>() -> Ptr<T>;
-
-module extern "intrinsic" fn stack_alloc_zeroed<T, const N: usize>() -> Ptr<T>;
+module extern "intrinsic" stack_alloc<T, const N: usize>() -> Ptr<T>;
+module extern "intrinsic" stack_alloc_zeroed<T, const N: usize>() -> Ptr<T>;
lib/zeta/os/fs/filesystem.zeta (3)

38-40: Unify async offsets with pread/pwrite (isize).

Use isize for asyncRead/asyncWrite to match pread/pwrite and Linux off_t semantics.

-    asyncRead(usize fd, Ptr<u8> buf, usize count, usize offset) -> isize;
-    asyncWrite(usize fd, Ptr<u8> buf, usize count, usize offset) -> isize;
+    asyncRead(usize fd, Ptr<u8> buf, usize count, isize offset) -> isize;
+    asyncWrite(usize fd, Ptr<u8> buf, usize count, isize offset) -> isize;

47-54: AsyncOp.offset should match API (isize).

Keep struct and methods consistent.

-    usize offset;       // optional for pread/pwrite
+    isize offset;       // optional for pread/pwrite

74-80: Avoid using the field name type — rename to d_type

Rename u8 type -> u8 d_type to avoid collision with the type keyword and to mirror linux_dirent64's d_type. Repo search shows DirEntry is only passed to getdents64 in lib/zeta/os/fs/linux.zeta and no .type accesses were found; change appears safe.

-type DirEntry {
+type DirEntry {
     usize ino;
     usize offset;
     usize reclen;
-    u8 type;
+    u8 d_type;
     Ptr<u8> name;
 }
lib/zeta/lang/maths.zeta (5)

41-49: floor/ceil don’t handle NaN/±Inf or out‑of‑range casts.

Casting NaN/Inf to i64 is undefined in many ABIs; also large |x| may overflow i64. Guard before casting.

Apply:

 inline floor(f64 x) -> f64 {
-    xi := x as i64
+    if (x != x) { return F64_NAN }               // NaN
+    if (x == 1.0/0.0) { return x }               // +Inf
+    if (x == -1.0/0.0) { return x }              // -Inf
+    if (x > (1<<63)-1 as f64) { return x }       // too large to cast
+    if (x < -(1<<63) as f64) { return x }
+    xi := x as i64
     if (x < xi as f64) { return (xi - 1) as f64 } else { xi as f64 }
 }
 
 inline ceil(f64 x) -> f64 {
-    xi := x as i64
+    if (x != x) { return F64_NAN }
+    if (x == 1.0/0.0 or x == -1.0/0.0) { return x }
+    if (x > (1<<63)-1 as f64 or x < -(1<<63) as f64) { return x }
+    xi := x as i64
     if (x > xi as f64) { return (xi + 1) as f64 } else { xi as f64 }
 }

63-84: ln() lacks downscale range reduction; very slow/unstable for x ≪ 1.

Add y<0.5 path (multiply by e, decrement n) and use a named E constant.

Apply:

-    while (y > 2.0) {
-        y /= 2.718281828459045
-        n += 1
-    }
+    const E: f64 = 2.71828182845904523536
+    while (y > 2.0) { y /= E; n += 1 }
+    while (y < 0.5) { y *= E; n -= 1 }

86-96: exp() needs argument reduction; series is inaccurate/slow for large |x|.

Reduce by ln2 and use 1/x symmetry for negatives to improve stability.

Example change (sketch):

const LN2: f64 = 0.6931471805599453
k := floor(x / LN2)
r := x - k * LN2
// series on r, then scale by 2^k

97-119: sin/cos need argument reduction; error explodes for large |x|.

Reduce x into [-PI, PI] (or tighter) before the series. Also consider 12–14 terms for target precision.

Apply:

-    mut term := x
-    mut sum := x
+    // Reduce x to [-PI, PI]
+    x := x - floor(x / (2.0 * PI)) * (2.0 * PI)
+    mut term := x
+    mut sum := x

(and analogously for cos)


121-123: tan() should guard division by ~0.

Return NaN (or ±Inf) when |cos(x)| underflows a small epsilon to avoid noisy infinities.

Suggested:

c := cos(x)
if (c == 0.0) { return F64_NAN }
sin(x) / c
lib/zeta/os/net/linux.zeta (2)

39-106: Duplicate wire types risk divergence across modules.

SockAddr*, IoVec, MsgHdr, etc., appear here and in other packages. Centralize these in a shared header to keep ABI/layout consistent.


161-164: select() uses syscall6 with a dummy 0; prefer syscall5 if available.

Passing an extra arg is harmless on x86_64 but obscures intent. If syscall5 exists in os_intrinsics, use it; otherwise leave a comment.

Apply:

-return os_intrinsics::syscall6(SYS_SELECT, nfds,
-        readfds.addr(), writefds.addr(), exceptfds.addr(), timeout.addr(), 0)
+return os_intrinsics::syscall5(SYS_SELECT, nfds,
+        readfds.addr(), writefds.addr(), exceptfds.addr(), timeout.addr())
lib/zeta/os/async/linux.zeta (1)

80-87: io_uring wrappers: match fs module usage and unify.

Keep signatures consistent with fs and net backends; ensure both call os_intrinsics::syscall6 with all six params.

lib/zeta/os/fs/linux.zeta (1)

43-46: open(): prefer O_DIRECTORY for opendir and pass mode only when O_CREAT.

Minor ergonomics tweak to avoid accidental file opens where a directory is expected.

lib/zeta/os/net/network.zeta (1)

96-99: hton/ntoh should return fixed-width types, not usize.**

Use u16/u32 to avoid accidental widening and UB on platforms where usize ≠ u{16,32}.

Apply:

-    hton16(usize v) -> usize;
-    ntoh16(usize v) -> usize;
-    hton32(usize v) -> usize;
-    ntoh32(usize v) -> usize;
+    hton16(u16 v) -> u16;
+    ntoh16(u16 v) -> u16;
+    hton32(u32 v) -> u32;
+    ntoh32(u32 v) -> u32;
engraver-assembly-emit/src/cranelift/cranelift_backend.rs (4)

124-132: Inconsistent handling of non-int operands in binary; avoid mixed todo!/unimplemented!.

Left side uses todo!(), right side unimplemented!(). Also, ConstBool isn’t supported. Prefer consistent handling and support booleans.

Apply:

-                    _ => todo!(),
+                    Operand::ConstBool(b) => builder.ins().iconst(types::I64, if *b { 1 } else { 0 }),
+                    _ => unimplemented!("Binary left: unsupported operand"),
@@
-                    _ => unimplemented!(),
+                    Operand::ConstBool(b) => builder.ins().iconst(types::I64, if *b { 1 } else { 0 }),
+                    _ => unimplemented!("Binary right: unsupported operand"),

156-165: Redundant lookup in Phi param map.

The or_else repeats the same key; drop it.

-                let idx = phi_param_map
-                    .get(&(curr_bb, *dest))
-                    .or_else(|| phi_param_map.get(&(curr_bb, *dest))) // redundant but explicit
-                    .expect("phi param mapping missing for dest");
+                let idx = phi_param_map
+                    .get(&(curr_bb, *dest))
+                    .expect("phi param mapping missing for dest");

526-551: Interpolation lowering likely incorrect callee and args.

This calls enum_new and treats args[0] as an array pointer. If the intent is string interpolation, this should use __interp (declared) and pass a pointer/len or a slice, not a single element.

Clarify intended ABI for interpolation. If it’s fn __interp(ptr, len) -> ptr, switch to self.interp_func and build a contiguous buffer (or an array object) before the call.


420-441: String data lacks a NUL terminator (if interoperating with C/OS).

If runtime consumers expect C-strings, append \0. Otherwise, keep as-is.

-        data_ctx.define(s.as_bytes().to_vec().into_boxed_slice());
+        let mut bytes = s.as_bytes().to_vec();
+        if !bytes.ends_with(&[0]) { bytes.push(0); }
+        data_ctx.define(bytes.into_boxed_slice());

Confirm expected string ABI in the runtime.

engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (5)

6-6: Remove unused import.

use std::io::Read; is unused.

-use std::io::Read;

66-69: Remove unused Dyn import and keep Module/Linkage.

cranelift_object::object::read::elf::Dyn isn’t used and can shadow names.

-use cranelift_module::{Linkage, Module};
-use cranelift_object::object::read::elf::Dyn;
+use cranelift_module::{Linkage, Module};

154-183: Syscall scaffold stubs will panic at runtime. Gate or implement.

runtime_syscall_signature and extract_args are todo!(). Either implement or guard call sites to avoid hits during testing.

I can wire a minimal syscallN ABI and arg extraction if you share the IR shape for syscalls.

Also applies to: 185-192


259-266: Tag size thresholds: double-check 65,536+ and 4,294,967,296+ bounds.

If variants equals 65,536 or 4,294,967,296, ensure it lands in the next bucket as intended.

Quick test expectations:

  • 256 -> 1 byte
  • 65,536 -> 2 or 4? Decide and encode accordingly.

351-358: PtrDeref load type must be a memory type.

If ret_hir maps to boolean and your convention is I8, ensure super::clif_type(&clif_ret) won’t yield b1. Otherwise Cranelift will reject the load.

Add an assertion or a mapping rule to translate boolean loads to I8.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59e7edc and 016a8e4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • emberforge-compiler/Cargo.toml (1 hunks)
  • emberforge-compiler/src/backend/grammar.pest (0 hunks)
  • emberforge-compiler/src/backend/mod.rs (0 hunks)
  • emberforge-compiler/src/midend/ir/expr_lowerer.rs (5 hunks)
  • engraver-assembly-emit/src/cranelift/cranelift_backend.rs (2 hunks)
  • engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (8 hunks)
  • input.zeta (2 hunks)
  • ir/Cargo.toml (1 hunks)
  • ir/src/sea_hasher.rs (0 hunks)
  • lib/zeta/intrinsics.zeta (1 hunks)
  • lib/zeta/lang/arrays.zeta (1 hunks)
  • lib/zeta/lang/maths.zeta (1 hunks)
  • lib/zeta/lang/mem.zeta (1 hunks)
  • lib/zeta/lang/pointer.zeta (1 hunks)
  • lib/zeta/lang/ranges.zeta (1 hunks)
  • lib/zeta/os/async/linux.zeta (1 hunks)
  • lib/zeta/os/async/threads.zeta (1 hunks)
  • lib/zeta/os/fs/filesystem.zeta (1 hunks)
  • lib/zeta/os/fs/linux.zeta (1 hunks)
  • lib/zeta/os/net/linux.zeta (1 hunks)
  • lib/zeta/os/net/network.zeta (1 hunks)
  • lib/zeta/os/time/linux_time_system.zeta (1 hunks)
  • lib/zeta/os/time/time_system.zeta (1 hunks)
  • lib/zeta/os_intrinsics.zeta (1 hunks)
  • scribe-parser/src/grammar.pest (6 hunks)
  • zetaruntime/Cargo.toml (1 hunks)
  • zetaruntime/src/fiber.rs (0 hunks)
💤 Files with no reviewable changes (4)
  • emberforge-compiler/src/backend/grammar.pest
  • emberforge-compiler/src/backend/mod.rs
  • ir/src/sea_hasher.rs
  • zetaruntime/src/fiber.rs
🧰 Additional context used
🧬 Code graph analysis (2)
emberforge-compiler/src/midend/ir/expr_lowerer.rs (3)
emberforge-compiler/src/midend/ir/lowerer.rs (3)
  • lower_operator_bin (35-50)
  • lower_type_hir (13-33)
  • new (67-106)
emberforge-compiler/src/midend/ir/module_lowerer.rs (1)
  • new (21-32)
zetaruntime/src/string_pool.rs (1)
  • resolve_string (57-62)
engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (2)
zetaruntime/src/arena.rs (1)
  • std (236-236)
engraver-assembly-emit/src/cranelift/cranelift_backend.rs (1)
  • new (53-82)
🔇 Additional comments (18)
input.zeta (1)

1-1: Struct → type: confirm nominal record semantics and mutability are preserved

Repo sweep found no remaining struct declarations and shows in-file uses that rely on mutable fields and aggregate initializers. Verify that type preserves: mutable fields (e.g. this.x *= …), method dispatch, and aggregate construction by compiling/running tests or checking the language spec.

Relevant locations:

  • input.zeta:1-4 — type Vec3f(...) and this.x *= by.x();
  • input.zeta:17-20 — type Vec9f(...) and this.x.multiply(by);
  • input.zeta:24-30 — x()/y()/z()/sum() accessors
  • input.zeta:34-37 — Vec3f { 1, 1, 1 } and Vec9f { ... } initializers
scribe-parser/src/grammar.pest (2)

1-3: Housekeeping: add COMMENT skipping if intended globally.

You defined COMMENT but didn’t combine it with WHITESPACE skipping in Pest settings if not already configured. Ensure the parser config skips both.


1-179: Fix & re-run reachability audit — script broken (sed + ripgrep regex errors).

Your run shows the audit failed: the "Defined rules" list printed line numbers (sed bug) and ripgrep/grep raised many regex parse errors. Use this corrected script (extracts rule names safely, searches as fixed strings) and re-run; paste results if it still fails.

#!/usr/bin/env bash
set -euo pipefail
file="scribe-parser/src/grammar.pest"
[ -f "$file" ] || { echo "File not found: $file" >&2; exit 2; }

echo "Defined rules:"
sed -nE 's/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=.*/\1/p' "$file"

echo
echo "Likely unreachable rules (defined but never referenced by others):"
rules=$(sed -nE 's/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=.*/\1/p' "$file")
while IFS= read -r r; do
  refs=$(rg -n --fixed-strings "$r" "$file" || true)
  # remove the definition line(s) like: 42:rule_name = ...
  refs_no_def=$(printf "%s\n" "$refs" | rg -v "^\\d+:\\s*$r\\s*=" || true)
  if [ -z "$refs_no_def" ]; then
    echo "  - $r"
  fi
done <<< "$rules"

echo
echo "Literals used as keywords (verify they appear in the keyword rule):"
rg -nP '"(public|private|protected|module|package|interface|impl|using|match|break|continue|defer|unsafe|extern|static|inline|noinline|sealed|const|own)"' "$file" | sed -E 's/^[[:space:]]*//'
zetaruntime/Cargo.toml (1)

7-13: Replace pre-release dashmap with stable 6.1.0 or justify keeping the RC

zetaruntime/Cargo.toml (lines 7–13): currently pins dashmap = "7.0.0-rc2" — latest stable is 6.1.0 (as of 2025-09-16). Use "dashmap = "6.1.0"" in the runtime unless an RC-only feature is required.

  • Verify whether both papaya and dashmap are needed; consolidate to a single concurrent-map dependency if one covers your use cases.
lib/zeta/lang/ranges.zeta (1)

27-32: Iterator logic is correct ([start, end)).

Yields inclusive start and excludes end; no off‑by‑one issues spotted.

lib/zeta/os/time/linux_time_system.zeta (1)

19-25: LGTM on the convenience wrappers.

The inline monotonic/realtime helpers are clean once clockGetTime is fixed.

lib/zeta/lang/arrays.zeta (1)

9-17: Confirm stack allocation lifetime; ensure inlining is honored.

Array.new/zeroed rely on stack_alloc escaping to caller. If inlining can be elided, this risks dangling pointers. Verify the compiler guarantees inlining here or add docs.

lib/zeta/lang/pointer.zeta (2)

5-11: LGTM on Ptr surface.

Constructors and wrappers line up with intrinsics once the module name is fixed.


13-15: Rename stor → store to match intrinsic and callers.

rg search for '\bstor\s*<' returned only the declaration at lib/zeta/lang/pointer.zeta:13 — rename is safe.

-inline mut stor<T>(Ptr<T> ptr, T value) = intrinsics::ptr_store<T>(ptr, value)
+inline mut store<T>(Ptr<T> ptr, T value) = intrinsics::ptr_store<T>(ptr, value)
lib/zeta/os/net/linux.zeta (3)

7-38: Syscall numbers look correct for x86_64.

Nice coverage and correct IDs for socket APIs and epoll/select.

If you have a constants module, consider reusing it to avoid drift across files.


124-142: accept/accept4 argument handling LGTM.

Addr and addrlen pointers are passed correctly; return type matches -errno convention.


144-151: TUNSETIFF constant and ioctl wrapper LGTM.

The value 0x400454ca matches Linux; openat use of AT_FDCWD is correct.

Ensure devname points to "/dev/net/tun" and flags include O_RDWR.

lib/zeta/os/async/linux.zeta (1)

72-78: epollPwait: argument order/width OK — scan inconclusive; verify no bare syscall[0-6] calls remain

Good use of syscall6 here; align all epoll wrappers to syscall6.

Run one of these locally and paste the output so I can re-check:

# ripgrep (preferred)
rg -nP --hidden -S '\bsyscall[0-6]\s*\(' -C1 || true

# fallback (if rg not available)
git grep -n -E 'syscall[0-6]\s*\(' || true
engraver-assembly-emit/src/cranelift/cranelift_intrinsics.rs (2)

22-28: LGTM: intrinsic name mapping aligned to ptr_*.


80-87: Good: route HIR types through clif_type_of then central super::clif_type.

Keeps one source of truth for CLIF types.

Also applies to: 101-103

engraver-assembly-emit/src/cranelift/cranelift_backend.rs (3)

15-15: Centralize CLIF type mapping — confirm definition & visibility

Importing crate::cranelift::clif_type is the right direction; I couldn't locate a clif_type definition in the repo with the provided search. Ensure clif_type is declared pub or pub(super), exported from crate::cranelift, and that this import doesn't introduce a module cycle.

Location: engraver-assembly-emit/src/cranelift/cranelift_backend.rs:15

Run to verify locally:

rg -n --type=rust '\bclif_type\b' -S -C3
rg -n --type=rust '\bfn\s+clif_type\s*\(' -S -C2

472-478: Reuse existing FuncId from self.func_ids — don't re-declare.

rg shows only this declare_function(&func.name) occurrence, but if functions are declared earlier (e.g. in emit_module) you must reuse the stored FuncId instead of calling declare_function again.

File: engraver-assembly-emit/src/cranelift/cranelift_backend.rs Lines: 472-478

-        let fid = self.module.declare_function(&func.name, Linkage::Local, &sig).unwrap();
+        let fid = *self.func_ids.get(&func.name)
+            .unwrap_or_else(|| panic!("FuncId for {} not declared", func.name));

281-286: Fix alloc: remove placeholder and compute size from type.

  • Remove the unused alloc_func placeholder and use the dedicated self.alloc_func.
  • Replace hardcoded 3 * 8 with a layout-derived size (use layout_of with TargetInfo / pointer size).
-                let size_bytes = 3i64 * 8; // <= conservative default; ideally compute from `ty`
-                let alloc_func = self.module.declare_func_in_func(self.interp_func /* placeholder? see below */, &mut builder.func);
-                // Actually we create a dedicated alloc FuncId in new() — use that (see below as `alloc_func_id`).
-                // Here I'll assume you added `self.alloc_func` as FuncId in the struct, so:
-                let alloc_ref = self.module.declare_func_in_func(self.alloc_func, &mut builder.func);
-                let size_val = builder.ins().iconst(types::I64, size_bytes);
+                // Compute object size using layout_of
+                let ptr_bytes = (self.module.isa().pointer_type().bytes() as u64);
+                let target = crate::cranelift::cranelift_intrinsics::TargetInfo { ptr_bytes };
+                let size_u64 = crate::cranelift::cranelift_intrinsics::layout_of(&_ty, &target)
+                    .expect("Alloc: cannot layout type")
+                    .size;
+                let alloc_ref = self.module.declare_func_in_func(self.alloc_func, &mut builder.func);
+                let size_val = builder.ins().iconst(types::I64, size_u64 as i64);
  • If SsaType for _ty can be unsized (slice/dyn), handle that case separately (runtime length or explicit size).

Comment on lines +17 to +19
rayon = "1.10.0"
smallvec = "1.15.0"
arrayvec = "0.7.6"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

New deps look fine; verify necessity and cleanup hashing deps.

  • Confirm arrayvec is actually used; drop if not.
  • You still depend on seahash elsewhere in this crate while IR moved to FxHash-based maps. If seahash is now unused here, remove it to avoid drift.
  • Edition 2024: make sure CI toolchain is pinned/new enough.

Run to check usage and prune:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "arrayvec usage:"
rg -n --type=rust -C2 '\bArrayVec\b|arrayvec::|use\s+arrayvec' || echo "No matches"

echo
echo "seahash usage in emberforge-compiler:"
rg -n --type=rust -C2 '\bseahash\b|SeaHash' emberforge-compiler || echo "No matches"

Length of output: 10521


Remove unused arrayvec; keep seahash.

  • arrayvec: no usages found — remove from emberforge-compiler/Cargo.toml (the new entry on lines ~17–19).
  • seahash: actively used (ir::sea_hasher::SeaHashBuilder / HashMap::with_hasher) in emberforge-compiler/src/midend/ir/{module_lowerer.rs, lowerer.rs, block_data.rs} — do not remove.
  • CI: ensure toolchain is pinned / recent enough for Edition 2024.
🤖 Prompt for AI Agents
In emberforge-compiler/Cargo.toml around lines 17 to 19, remove the unused
dependency entry for arrayvec (delete the arrayvec = "0.7.6" line) while leaving
seahash intact; verify no other references to arrayvec remain in the repo and
run cargo check to confirm. Also ensure the CI toolchain is pinned or updated to
a Rust version that supports Edition 2024 (update rust-toolchain or CI config
accordingly) so the project builds cleanly.

HirExpr::Binary { left, op, right } =>
self.lower_expr_binary(left, op, right),

HirExpr::Ident(name) => self.var_map[name],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Indexing bug: missing reference causes type mismatch.

Use a reference when indexing var_map.

Apply:

-            HirExpr::Ident(name) => self.var_map[name],
+            HirExpr::Ident(name) => self.var_map[&name],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HirExpr::Ident(name) => self.var_map[name],
HirExpr::Ident(name) => self.var_map[&name],
🤖 Prompt for AI Agents
In emberforge-compiler/src/midend/ir/expr_lowerer.rs around line 62, the branch
HirExpr::Ident(name) indexes var_map with name by value causing a type mismatch;
change the index to use a reference (e.g. self.var_map[&name]) or otherwise
borrow the key when accessing var_map so the lookup type matches the map's key
type.

Comment on lines +102 to +133
fn handle_ident(&mut self, op: AssignmentOperator, rhs: Value, name: StrId) -> Value {
let var_val = self.var_map[&name];

// Determine if this is a simple assign or a compound assign
let result = match op {
AssignmentOperator::Assign => rhs,
AssignmentOperator::AddAssign
| AssignmentOperator::SubtractAssign
| AssignmentOperator::MultiplyAssign
| AssignmentOperator::DivideAssign
| AssignmentOperator::ModuloAssign
| AssignmentOperator::BitAndAssign
| AssignmentOperator::BitOrAssign
| AssignmentOperator::BitXorAssign
| AssignmentOperator::ShiftLeftAssign
| AssignmentOperator::ShiftRightAssign => {
let dest = self.new_value();
let bin_op = assign_op_to_bin_op(op);

self.emit(Instruction::Binary {
dest,
op: bin_op,
left: Operand::Value(var_val),
right: Operand::Value(rhs),
});
dest
}
};

self.var_map.insert(name.clone(), result);
result
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

assign_op_to_bin_op is used but not defined/imported.

This will not compile. Add the helper mapping.

Apply at file end (before the final closing brace or after the impl):

+// Maps compound assignment ops to the corresponding binary op.
+fn assign_op_to_bin_op(op: AssignmentOperator) -> BinOp {
+    match op {
+        AssignmentOperator::AddAssign => BinOp::Add,
+        AssignmentOperator::SubtractAssign => BinOp::Sub,
+        AssignmentOperator::MultiplyAssign => BinOp::Mul,
+        AssignmentOperator::DivideAssign => BinOp::Div,
+        AssignmentOperator::ModuloAssign => BinOp::Mod,
+        AssignmentOperator::BitAndAssign => BinOp::BitAnd,
+        AssignmentOperator::BitOrAssign => BinOp::BitOr,
+        AssignmentOperator::BitXorAssign => BinOp::BitXor,
+        AssignmentOperator::ShiftLeftAssign => BinOp::Shl,
+        AssignmentOperator::ShiftRightAssign => BinOp::Shr,
+        AssignmentOperator::Assign => unreachable!("plain Assign is handled earlier"),
+    }
+}

If some BinOp variants don’t exist yet, either add them to ssa_ir::BinOp or gate unsupported ops with unimplemented! to keep the build green.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn handle_ident(&mut self, op: AssignmentOperator, rhs: Value, name: StrId) -> Value {
let var_val = self.var_map[&name];
// Determine if this is a simple assign or a compound assign
let result = match op {
AssignmentOperator::Assign => rhs,
AssignmentOperator::AddAssign
| AssignmentOperator::SubtractAssign
| AssignmentOperator::MultiplyAssign
| AssignmentOperator::DivideAssign
| AssignmentOperator::ModuloAssign
| AssignmentOperator::BitAndAssign
| AssignmentOperator::BitOrAssign
| AssignmentOperator::BitXorAssign
| AssignmentOperator::ShiftLeftAssign
| AssignmentOperator::ShiftRightAssign => {
let dest = self.new_value();
let bin_op = assign_op_to_bin_op(op);
self.emit(Instruction::Binary {
dest,
op: bin_op,
left: Operand::Value(var_val),
right: Operand::Value(rhs),
});
dest
}
};
self.var_map.insert(name.clone(), result);
result
}
fn handle_ident(&mut self, op: AssignmentOperator, rhs: Value, name: StrId) -> Value {
let var_val = self.var_map[&name];
// Determine if this is a simple assign or a compound assign
let result = match op {
AssignmentOperator::Assign => rhs,
AssignmentOperator::AddAssign
| AssignmentOperator::SubtractAssign
| AssignmentOperator::MultiplyAssign
| AssignmentOperator::DivideAssign
| AssignmentOperator::ModuloAssign
| AssignmentOperator::BitAndAssign
| AssignmentOperator::BitOrAssign
| AssignmentOperator::BitXorAssign
| AssignmentOperator::ShiftLeftAssign
| AssignmentOperator::ShiftRightAssign => {
let dest = self.new_value();
let bin_op = assign_op_to_bin_op(op);
self.emit(Instruction::Binary {
dest,
op: bin_op,
left: Operand::Value(var_val),
right: Operand::Value(rhs),
});
dest
}
};
self.var_map.insert(name.clone(), result);
result
}
// Maps compound assignment ops to the corresponding binary op.
fn assign_op_to_bin_op(op: AssignmentOperator) -> BinOp {
match op {
AssignmentOperator::AddAssign => BinOp::Add,
AssignmentOperator::SubtractAssign => BinOp::Sub,
AssignmentOperator::MultiplyAssign => BinOp::Mul,
AssignmentOperator::DivideAssign => BinOp::Div,
AssignmentOperator::ModuloAssign => BinOp::Mod,
AssignmentOperator::BitAndAssign => BinOp::BitAnd,
AssignmentOperator::BitOrAssign => BinOp::BitOr,
AssignmentOperator::BitXorAssign => BinOp::BitXor,
AssignmentOperator::ShiftLeftAssign => BinOp::Shl,
AssignmentOperator::ShiftRightAssign => BinOp::Shr,
AssignmentOperator::Assign => unreachable!("plain Assign is handled earlier"),
}
}
🤖 Prompt for AI Agents
In emberforge-compiler/src/midend/ir/expr_lowerer.rs around lines 102 to 133,
the call to assign_op_to_bin_op is missing and causes a compile error; add a
helper function (placed at the end of the file before the final closing brace or
after the impl) that maps AssignmentOperator variants to the corresponding
ssa_ir::BinOp variants and returns the BinOp for use in the Binary instruction;
for any AssignmentOperator that has no corresponding ssa_ir::BinOp either add
the missing BinOp variant(s) to ssa_ir::BinOp or handle those cases in the
helper by returning an explicit unimplemented!() (or similar) to keep the build
green.

Comment on lines 261 to 270
let cls_name = match self.current_block_data.value_types.get(&obj_val) {
Some(SsaType::Ptr(inner)) => match &**inner { SsaType::User(name) => name.clone(), other => panic!("FieldAccess on non-user pointer type {:?}", other) },
Some(SsaType::User(name, args) ) => {
if let Some(inner_value_name) = self.string_pool.resolve_string(name).split("_").last() {
StrId(self.string_pool.intern(inner_value_name))
} else {
panic!("Could not determine object's class for FieldAccess.")
}
}
other => panic!("Could not determine object's class for FieldAccess: {:?}", other),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fragile class-name parsing via string split. Use canonical helper.

Splitting on "_" will break for names containing underscores. Use optimized_string_buffering::get_type.

Apply:

-        let cls_name = match self.current_block_data.value_types.get(&obj_val) {
-            Some(SsaType::User(name, args) ) => {
-                if let Some(inner_value_name) = self.string_pool.resolve_string(name).split("_").last() {
-                   StrId(self.string_pool.intern(inner_value_name))
-                } else {
-                    panic!("Could not determine object's class for FieldAccess.")
-                }
-            }
+        let cls_name = match self.current_block_data.value_types.get(&obj_val) {
+            Some(SsaType::User(name, _args)) => {
+                optimized_string_buffering::get_type(*name, self.string_pool)
+            }
             other => panic!("Could not determine object's class for FieldAccess: {:?}", other),
         };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let cls_name = match self.current_block_data.value_types.get(&obj_val) {
Some(SsaType::Ptr(inner)) => match &**inner { SsaType::User(name) => name.clone(), other => panic!("FieldAccess on non-user pointer type {:?}", other) },
Some(SsaType::User(name, args) ) => {
if let Some(inner_value_name) = self.string_pool.resolve_string(name).split("_").last() {
StrId(self.string_pool.intern(inner_value_name))
} else {
panic!("Could not determine object's class for FieldAccess.")
}
}
other => panic!("Could not determine object's class for FieldAccess: {:?}", other),
};
let cls_name = match self.current_block_data.value_types.get(&obj_val) {
Some(SsaType::User(name, _args)) => {
optimized_string_buffering::get_type(name, &self.string_pool)
}
other => panic!("Could not determine object's class for FieldAccess: {:?}", other),
};
🤖 Prompt for AI Agents
In emberforge-compiler/src/midend/ir/expr_lowerer.rs around lines 261 to 270,
the code extracts the class name by splitting the interned string on "_" which
is fragile for names containing underscores; replace that logic with
optimized_string_buffering::get_type to obtain the canonical type name, then
intern that returned type name and wrap with StrId as before (handle any
Option/Result the helper returns and keep the existing panic path if it cannot
resolve). Ensure you call self.string_pool.intern on the helper's type string
and construct StrId(...) instead of using split("_").last().

Comment on lines +330 to +347
let maybe_cls_name: Option<SsaType> = self.current_block_data.value_types.get(&obj_val).cloned();
let maybe_cls_name: Option<&str> = match maybe_cls_name {
Some(SsaType::User(ref name, _args)) => self.string_pool.resolve_string(name).split("_").last(),
_ => None
};

let cls_name_id: Option<StrId> = if let Some(cls_name) = maybe_cls_name {
Some(StrId(self.string_pool.intern(cls_name)))
} else {
None
};

if let Some(value) = self.emit_call(field, obj_val, &mut operands, cls_name_id) {
return value;
}

let direct_name: StrId = optimized_string_buffering::build_scoped_name(maybe_cls_name, field, self.string_pool);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same underscore-split issue in method calls; normalize via get_type.

Avoid duplicating parsing logic; derive cls_name_id from get_type and pass a resolved &str to build_scoped_name.

Apply:

-        let maybe_cls_name: Option<SsaType> = self.current_block_data.value_types.get(&obj_val).cloned();
-        let maybe_cls_name: Option<&str> = match maybe_cls_name {
-            Some(SsaType::User(ref name, _args)) => self.string_pool.resolve_string(name).split("_").last(),
-            _ => None
-        };
-
-        let cls_name_id: Option<StrId> = if let Some(cls_name) = maybe_cls_name {
-            Some(StrId(self.string_pool.intern(cls_name)))
-        } else {
-            None
-        };
+        let cls_name_id: Option<StrId> = match self.current_block_data.value_types.get(&obj_val) {
+            Some(SsaType::User(name, _)) => Some(optimized_string_buffering::get_type(*name, self.string_pool)),
+            _ => None,
+        };
+        let maybe_cls_name: Option<&str> = cls_name_id
+            .as_ref()
+            .map(|id| self.string_pool.resolve_string(&**id));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In emberforge-compiler/src/midend/ir/expr_lowerer.rs around lines 330 to 347,
replace the current ad-hoc underscore-split and duplicated parsing by using the
existing get_type(obj_val) path: call self.get_type(obj_val) to obtain the
SsaType, match SsaType::User to resolve the name string with
self.string_pool.resolve_string(...) (without splitting on "_"), create
cls_name_id from that resolved &str (interning into StrId), pass the resolved
&str (or None) into optimized_string_buffering::build_scoped_name, and remove
the duplicate underscore-splitting logic so cls_name_id and build_scoped_name
both derive from the same normalized type/name source.

@@ -0,0 +1,13 @@
package zeta.os.time

struct TimeSpec(isize sec, isize nsec);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use fixed 64‑bit fields for TimeSpec for cross‑arch correctness.

isize maps to 32‑bit on 32‑bit targets, but Linux __kernel_timespec uses 64‑bit fields. Using isize risks ABI/layout mismatches on 32‑bit.

-struct TimeSpec(isize sec, isize nsec);
+struct TimeSpec(i64 sec, i64 nsec);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct TimeSpec(isize sec, isize nsec);
struct TimeSpec(i64 sec, i64 nsec);
🤖 Prompt for AI Agents
In lib/zeta/os/time/time_system.zeta around line 3, the TimeSpec struct
currently uses isize for sec and nsec which can be 32‑bit on 32‑bit targets and
lead to ABI/layout mismatches with Linux __kernel_timespec; change both fields
to fixed 64‑bit signed types (i64) so sec and nsec are always 64 bits, update
any code that constructs or reads TimeSpec to use i64 values, and run/adjust any
FFI bindings or serialization logic that depends on the old layout to ensure
compatibility.

Comment on lines +8 to +13
statem_decl = { "statem" ~ ident ~ "{" ~ transition* ~ "}" }

interface_decl = { visibility_modifier? ~ "interface" ~ ident ~ "(" ~ param_list ~ ")" }

transition = { state_ref ~ "=>" ~ state_ref ~ ";" }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unreachable top-level rules (not included in stmt): statem_decl, interface_decl, extern_block, impl_decl, type_decl.

These constructs can’t be parsed because they aren’t referenced from stmt/block_stmt. Also, type_decl conflicts with struct_decl ("type …") unless ordered/disambiguated.

Apply these diffs to wire them in and avoid conflicts:

- block_stmt = _{ if_stmt | while_stmt | for_stmt | match_stmt | fun_decl | struct_decl | enum_decl | unsafe_block | block }
+ block_stmt = _{ if_stmt | while_stmt | for_stmt | match_stmt
+               | fun_decl | struct_decl | type_decl | enum_decl
+               | interface_decl | impl_decl | extern_block | statem_decl
+               | unsafe_block | block }

- simple_stmt = _{ (import_stmt | let_stmt | const_stmt | return_stmt | expr_stmt | break_stmt | continue_stmt | defer_stmt) ~ stmt_terminator? }
+ simple_stmt = _{ (package_stmt | import_stmt | let_stmt | shorthand_let_stmt
+                 | const_stmt | return_stmt | expr_stmt | break_stmt | continue_stmt | defer_stmt) ~ stmt_terminator? }

To prevent ambiguity between struct_decl and type_decl, either place type_decl before struct_decl in block_stmt (as above) or fold the aliasing into struct_decl:

-struct_decl = { visibility_modifier? ~ "type" ~ ident ~ generic_params? ~ param_list? }
+struct_decl = { visibility_modifier? ~ "type" ~ ident ~ generic_params? ~ ( "=" ~ type_expr | param_list ) }
- type_decl = { "type" ~ ident ~ "=" ~ ident }
+// type_decl can now be removed, or kept for back-compat delegating to the same form.

Also applies to: 27-31, 69-70, 73-74, 93-94

🤖 Prompt for AI Agents
In scribe-parser/src/grammar.pest around lines 8-13 (and similarly apply to the
other referenced ranges 27-31, 69-70, 73-74, 93-94), several top-level rules
(statem_decl, interface_decl, extern_block, impl_decl, type_decl) are
unreachable because they are not referenced from stmt or block_stmt and
type_decl conflicts with struct_decl; update the grammar to (1) include these
rules into the appropriate production(s) so they are reachable (e.g., add
statem_decl, interface_decl, extern_block, impl_decl, and type_decl into stmt
and/or block_stmt alternatives where other top-level decls live), and (2)
resolve the "type …" vs "struct …" ambiguity by either placing type_decl before
struct_decl in the block_stmt/stmt alternatives or by folding aliasing into
struct_decl (merge the alias form into the struct_decl alternative) so parsing
is disambiguated.

Comment on lines +16 to +24
qualified_type = { generic_type ~ ("." ~ ident)* }

transition = {
state_ref ~ "=>" ~ state_ref ~ ";"
generic_type = {
ident ~ generic_params?
| ident ~ "<" ~ generic_placeholder ~ ("," ~ generic_placeholder)* ~ ">"
}

generic_placeholder = { ident ~ (":" ~ ident)? }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Generic type syntax is internally inconsistent; second alternative is shadowed.

generic_type’s first alt (ident ~ generic_params?) will greedily match “<…>” with generic_params (declaration form), making the second alt (use-site placeholders) unreachable. Use type arguments (type_expr) for use sites.

-qualified_type = { generic_type ~ ("." ~ ident)* }
-
-generic_type = {
-    ident ~ generic_params?
-  | ident ~ "<" ~ generic_placeholder ~ ("," ~ generic_placeholder)* ~ ">"
-}
-
-generic_placeholder = { ident ~ (":" ~ ident)? }
+qualified_type = { generic_type ~ ("." ~ ident)* }
+generic_type = { ident ~ ("<" ~ type_expr ~ ("," ~ type_expr)* ~ ">")? }
+// generic_placeholder no longer needed at use sites; remove or repurpose if still used elsewhere.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
qualified_type = { generic_type ~ ("." ~ ident)* }
transition = {
state_ref ~ "=>" ~ state_ref ~ ";"
generic_type = {
ident ~ generic_params?
| ident ~ "<" ~ generic_placeholder ~ ("," ~ generic_placeholder)* ~ ">"
}
generic_placeholder = { ident ~ (":" ~ ident)? }
qualified_type = { generic_type ~ ("." ~ ident)* }
generic_type = { ident ~ ("<" ~ type_expr ~ ("," ~ type_expr)* ~ ">")? }
// generic_placeholder no longer needed at use sites; remove or repurpose if still used elsewhere.
🤖 Prompt for AI Agents
In scribe-parser/src/grammar.pest around lines 16 to 24, the first alternative
of generic_type (ident ~ generic_params?) greedily matches angle-bracket forms
and therefore shadows the second alternative; to fix this, split declaration vs
use-site forms so they are unambiguous: make the declaration form only match a
named generic-parameter list (rename or constrain generic_params to a
declaration-specific rule) and make the use-site form use type arguments
(type_expr) instead of generic_placeholder; either reorder with a more specific
lookahead for the use-site "< ... >" case or change the first alternative so it
does not optionally consume "<...>" (so the second alternative becomes
reachable).

Comment on lines +51 to 60
import_stmt = { "using" ~ string }
package_stmt = { "package" ~ (ident ~ ("." ~ ident)*) }
let_stmt = { mut_keyword? ~ var_type ~ atom ~ "=" ~ expr }
shorthand_let_stmt = { mut_keyword? ~ atom ~ ":=" ~ expr }
const_stmt = { "const" ~ var_type ~ ident ~ "=" ~ expr }
return_stmt = { "return" ~ expr? }
if_stmt = { "if" ~ "(" ~ expr ~ ")" ~ block ~ (("else" ~ (block | if_stmt))?) }
while_stmt = { "while" ~ "(" ~ expr ~ ")" ~ block }
for_stmt = { "for" ~ "(" ~ atom ~ ":=" ~ expr ~ ")" ~ block }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Reserve all language keywords to avoid identifiers like “using”, “package”, “interface”, “match”, etc.

keyword omits many tokens introduced in this grammar, allowing them as identifiers and causing surprising parses.

-import_stmt = { "using" ~ string }
+import_stmt = { "using" ~ string }
@@
-keyword = _{ "true" | "false" | "if" | "else" | "while" | "return" | "let" | "for" | "enum" | "type" }
+keyword = _{ "true" | "false" | "if" | "else" | "while" | "return" | "let" | "for"
+           | "enum" | "type" | "match" | "break" | "continue" | "defer"
+           | "unsafe" | "extern" | "static" | "inline" | "noinline" | "sealed"
+           | "using" | "package" | "module" | "public" | "private" | "interface"
+           | "impl" | "const" | "own" }

Optionally reserve builtin type names too (i32, u64, boolean, str, …) if they shouldn’t be valid identifiers.

Also applies to: 115-118

Comment on lines +142 to 145
primary = { atom ~ ( ( ("." | "?.") ~ atom ) | call_args)* }
array_literal = { "[" ~ (expr ~ ("," ~ expr)*)? ~ "]" }
atom = _{ parenthesized_expr | ident | number | string | class_initialization | boolean | array_literal }
atom = _{ parenthesized_expr | pattern | class_initialization | boolean | array_literal }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Indexing suffix is defined but never used; also it should accept any expr, not atom.

Currently primary can’t parse a[b]. Wire array_suffix into primary and allow full expressions as indices.

-primary = { atom ~ ( ( ("." | "?.") ~ atom ) | call_args)* }
+primary = { atom ~ ( ( ("." | "?.") ~ atom ) | call_args | array_suffix )* }
@@
-array_suffix = { "[" ~ atom ~ "]" }
+array_suffix = { "[" ~ expr ~ "]" }

Also applies to: 166-167

🤖 Prompt for AI Agents
In scribe-parser/src/grammar.pest around lines 142-145 (and similarly at
166-167), the primary rule currently never uses the indexing suffix and the
index is limited to atom; update primary to include the indexing suffix in its
repetition (e.g. allow ( ("." | "?.") ~ atom | array_suffix | call_args )* ),
and change the array_suffix definition so the brackets contain expr (not atom)
so full expressions can be used as indices; ensure the array_literal rule
remains for literal syntax and that the array_suffix rule is referenced where
dotted/call suffixes are handled.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

UTG Post-Process Complete

No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Creating a PR to put the unit tests in...

The changes have been created in this pull request: View PR

@FlameyosSnowy FlameyosSnowy merged commit 1dd6af2 into main Sep 16, 2025
2 checks passed
@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Caution

CodeRabbit timed out while planning changes for emberforge-compiler/tests/test_backend_parser.rs. Skipping this file.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

UTG Post-Process Complete

No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Creating a PR to put the unit tests in...

The changes have been created in this pull request: View PR

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

UTG Post-Process Complete

No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Creating a PR to put the unit tests in...

The changes have been created in this pull request: View PR

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants