The `t`/`c` modules are gone, so their C type aliases (CInt*, CUnsigned*, CSizeT, CBool, CVoid, CFloat, CDouble, CChar, t.State, t.DefineT, t.VolatileT, the c.CIf family, @c.CReturn) are no longer recognised or reported anywhere: sign inference, enum widths, stub generation/reading, expression lookups and the LSP type-alias check. `import t` / `import c` no longer gets a special message.
Compiled callees register a per-parameter nullable mask (self included for methods, specializations included); cross-module callees fall back to their vpi param annotations. A nullable binding/field argument now fails instead of silently narrowing.
- ExprGenOps called `_lookup_module_sha1` on ExprGenAttr, but the method
lives on CallGenBase. With ExprGenOps now translated before the
callee's stub/text.ll is available, the receiver lookup failed with
"method return type cannot be obtained precisely":
`cls=ExprGenAttr ... _lookup_module_sha1`. Use
`self.Gen.CallGenBase._lookup_module_sha1`.
- VpiReader._lookup_vpi_ret_annot's generic-specialization fallback used
`sha1_len` without declaring it. The new `list[int].set` return-type
lookup reaches that fallback and failed with
`NameError: undefined name 'sha1_len'`. Compute
`sha1_len = strlen(src_sha1)` before the cache scan.
Self-host 110/110 (two generations); ListTest, GenericTest3, PtrOpProbe
and HashLibTest pass.
Replace the two near-identical VPI annotation line scanners with one marker scanner parameterized by annotation kind. Source-only change: 91 added, 123 deleted (net -32 lines).
Suppress DEBUG output at the default INFO level, share the VPI pre-registration walker, and share the .vpi read path between the annotation cache loaders. Source-only change: 53 added, 81 deleted (net -28 lines).
Drop unreachable struct/type helpers, the legacy wrapper-main and build pipeline paths, debug-only scaffolding, unused logging helpers/constants, and the no-op LSP signature map. Add the audit report for the remaining duplication and logging work.
A file opts into the strict NAFTAG lifetime mode with a leading
from __future__ import __cwa__
and only that file changes: every other module keeps the legacy @life
escape analysis, so existing code is untouched.
- detect_strict_cwa publishes the flag per file (reset before each
translation, also visible to the .vpi pass); the directive must be the
file's first statement, a module docstring may precede it
- the directive is not a module import: import translation returns early
and the .vpi writer skips it, so importers never look for `__future__`
- strict files reject @life/@willFree with LifetimeError; the OBS
call-site contracts are the only boundary syntax there
- Translator.StrictCWA carries the mode for the current file
ViperC is now self-hosted, so its own source can use `in (...)`/`[...]`/`{...}`.
The vpi scanner compared raw byte values (0x22/0x27/0x28/...) and now reads
if ch in ('"', '\''): quote = ch
elif ch in ('(', '[', '{'): depth += 1
which is the same lowering the compiler emits for user code (OR of equality
tests, string literals through strcmp).
- `x in (a,b,c)` / `[..]` / `{..}` lowers to an OR of equality tests (string
literals via strcmp) and `not in` negates it; literal containers used to
silently evaluate to false
- add VpiText (ParenEnd/ParamSpans/ParamCount/HasVararg/FindDefaultEq) and
drop the five hand-written copies of the parameter scanner; escape skips no
longer jump past the parameter-list end
- fill omitted arguments from recorded defaults before any zero-fill, and fail
fast when no default is recorded (memcpy keeps its intrinsic lowering)
- resolve package-qualified `extern def` calls without a SHA1 prefix:
explicit allow_star=1, rel-path module fallback, raw vpi bare-declaration
scan, and `extern def`/`export def` flags honoured during declare injection
- fix exact-size read_all usage in VpiStub.Load/Ensure and the vpi scanner
- add VpiStub: fixed-layout blob written next to every .vpi (dual write at all
writers, Ensure() backfills a missing or stale companion on cache reuse)
- serve arity/signature queries for top-level functions from the blob, with a
silent fallback to the text scanner (methods, varargs, duplicates, corrupt file)
- prefer the injected declare over the text arity in the cross-module check
- CDefine/type text indexes and in-place SSA renumbering and bulk appends
- WIP: char* conversion in CallGenBase and CallGenLeafKit
Module-level functions and class methods each carried their own copy of "resolve the
return type", "create the parameter allocas" and "emit the implicit epilogue". The copies
drifted: an unannotated method was still typed i32 by the class-method body path while its
declaration said void, so the epilogue emitted `llvm.return 0 : i32` inside a void function
and the MLIR verifier rejected it ("'llvm.return' op expected no operands", Scheduler._yield).
- FuncGenBase.resolve_func_ret_ty: the single return-type policy (annotation -> storage
marker -> __init__/__new__ hooks -> unannotated = procedure void -> fail-fast). Used by
the forward-declare pass, the body pass, class methods and generic specializations
- FuncGenBase.param_llvm_ty: the single parameter mapping (CVoid skipped, bare class ->
Ptr(struct), ArrayT -> element pointer), used by every signature and alloca site
- FuncGenBase.emit_func_epilogue: the single implicit-return implementation (procedure ->
ret void, unreachable merge, fail-fast for an annotated fall-through), shared by module
functions, methods and nested functions
- FuncGenBase.translate_func_body: the shared body pipeline (scope entry, self/parameter
storage, *args va_list, try/except out-params, pre-scan, capture preparation, slife,
statements, epilogue, state restore). ClassGenVTable._translate_method now only handles
method naming, vtable/overload registration and add_param
- the same policy reaches cross-module and generic declares: unannotated vpi functions and
methods declare void, generic specialization declares use the shared resolver, and the
list.append call sites emit a void call
- method parameters now carry the const / volatile / pointee-volatile / pure markers that
the module-level path always had
- FuncGenMixin: the two dry-run helpers that return ints are annotated, as the language
now requires (an unannotated def is a procedure)
Verified: 41 Test projects pass (MethodProcTest, CrossModuleSigTest, RawPtrParamTest,
AstTest, ListTest, OopInheritTest, ...), ViperC self-hosts with the new compiler, and the
6 tests that still fail reproduce identically with the previous compiler.
Unannotated functions were typed by scanning the body, which inferred i32, so falling off
the end emitted `llvm.return 0 : i32` inside a function declared void. The MLIR verifier
rejects that ("expected no operands"), which is what broke Scheduler._yield.
- an unannotated `def` is void in both the forward declare and the body, so the implicit
epilogue emits `ret void`
- translate_return treats a procedure (no annotation, `-> None`, or a void-typed function) as
void: the expression is evaluated for its side effects and `ret void` is emitted. Bare
`return`, `return f()` where f is void, and `return <None value>` are legal and never carry
a value
- body_has_value_return accepts `return f()` and `return <None define>` in a procedure
- `define X = None` is a named None: registered as the string sentinel None, materialized as
a None value at use sites, and exported through the vpi
Injecting a dependency's module function deduped through a suffix match, so {other_sha}.init already in the module suppressed the bare @init declare of this module's export. Calls to tty.init(...) then suffix-matched the other module's init (1 parameter) and failed the arity check; the call-site module check also accepted a mangled symbol from another module whenever this module exported any function with that name.
- dedup a bare ExportT/State symbol by exact name only (VpiReader._inject_module_declare)
- accept a foreign-module binding only for bare symbols (the export/state form) or a re-export owner (CallGenMixin)
LookupSignDefaultsByName subtracted one receiver slot from every class-level def, but the vpi keeps the declared parameter list: a @staticmethod's first parameter is a real argument. Scheduler.exit_process(pid: int) therefore reported 0 parameters and the call-site arity check rejected exit_process(p.pid). Subtract the receiver only when the first parameter is actually named self/cls.
Two follow-ups to the cross-module signature check:
- A method without a return annotation is void, like a module-level `def`. The vpi
return-annotation cache recorded it as a hardcoded i32, so callers invented an i32 return
for a void method. It now records `void` for non-extern declarations (methods included).
- A class-level function declared without `self` (`Class.func()`) is a namespaced plain
function, not a method bound to a receiver. The module-qualified call path rejected such
symbols as method bindings, which then fell through to the external-C whitelist and failed
on a void return (`Scheduler._yield`).
- A pointer argument passed to an integer-typed parameter is converted at the call boundary
instead of failing the implicit pointer-to-integer rule. A `rawptr` value whose callee
parameter degraded to uint64 (stale/mis-typed stub) now reaches the callee with its address
instead of aborting with a CastError.
mlirlite.build_call composes the llvm.call function type from the argument values and the
caller-supplied return type; it never consults the callee. Declares are synthesized the same
way (_ensure_method_declare appends missing params from the caller's own argument values and
never compares return types), so nothing verified that a call site agreed with the definition.
Two modules could therefore emit incompatible calls for the same symbol, which only the MLIR
verifier rejected later:
llvm.func @MapHHDM(i64, i64, i64) (definition, void)
llvm.call @MapHHDM(i64, i64, i32) -> i32 (caller)
- after the declare/definition exists, conform every fixed argument to its parameter type and
emit the call with the symbol's return type (_conform_call_args / _call_symbol_ret_ty)
- fail fast when a cross-module call site resolves a return type the symbol disagrees with,
instead of emitting both and letting mlir-opt report an operand type mismatch
- record a module-level `def` without a return annotation as `void` in the vpi return-annotation
cache (previously a hardcoded i32, so callers invented an i32 return for void exports);
extern/state C declarations and methods keep the legacy default
`define GW_F = double(GW)` was registered as an integer constant: the DefineT
registration only took the float path when the initializer contained a float
literal, and the cast was unwrapped before the value was read. A float cast
produces a float even without a literal, so the define became an i16 640 and
define GW_F = double(GW)
define GH_F = double(GH)
aspect: double = GW_F / GH_F
folded with integer division to 1 instead of 16/9, which broke Gargantua's
projection (and any aspect/scale ratio derived the same way).
- decide the constant kind from the original initializer (a cast to a float
type, a float literal, or a reference to a float define) and evaluate it as
a double, keeping the f32/f64 choice from the annotation or `float(...)`
- resolve DefineT names inside float expressions, so `double(GW)` and
`double(A) / double(B)` evaluate at compile time
- .vpi generation writes such defines as float literals (`= 640.0`) instead of
running the integer evaluator, which also fixes a SIGFPE when that evaluator
hit `0 / 0` for a float expression; integer division/modulo now guard a zero
right operand instead of trapping the compiler