Commit Graph
987 Commits
Author SHA1 Message Date
TermiNexus 4bf04c2452 feat(vpc): binary .vpb stub companion and str->char* migration in call gen
- 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
2026-10-31 16:25:18 +08:00
TermiNexus 451d89b39a feat(vpf): default to level 3 (counts + time + edges) when --spend-file is given 2026-10-30 22:26:02 +08:00
TermiNexus 91e188c539 feat(vpc): vprofile instrumentation (--spend-file/--vpf) + verbatim --build-self forwarding
--spend-file <path> instruments every compiled function with
vpf_enter(id, name) / vpf_leave(id); main additionally calls
vpf_install(path, level). includes/vprofile.vp keeps the tables (no
allocation on the hot path), times with QPC and writes the report from
atexit, so normal return and exit() both produce it.

  --vpf 0 off | 1 call counts | 2 +per-function time (default) |
  3 +caller/callee edges

*A report path is resolved against the output exe directory, so
  ../prof.txt and absolute paths behave as documented regardless of the
  process cwd.
*Instrumented include objects use their own cache suffix
  (.binary.vpf<N>, stacked with the asan/traceback suffixes) and that
  cache is cleared once when profiling is enabled.
*StubMergerBuild force-adds the vprofile runtime to the reachable set;
  FuncGenBase emits a declare on demand because the instrumented module
  usually does not import vprofile.
*--repl is rejected: the fragment DLL is loaded at runtime and does not
  link the runtime.

--build-self no longer keeps a whitelist: SelfBuild.CollectForwardedArgs
drops only --build-self / --project / --build-dir and forwards every
other argument verbatim, so a self-build can enable the same modes as a
normal build.
2026-10-30 22:24:01 +08:00
TermiNexus c9d33bc202 build(phaseb): move llvm-link + opt for raw-IR modules into ircore
Raw-IR modules (`__llvmir__` / `__mlir__`) were the last common case that still needed
child processes: llvm-link merged the `.raw.ll` sidecar into the translated module, then
`opt -passes=always-inline,globaldce` inlined the raw bodies. Both now run inside the DLL:

- new `ircore_mlir_to_obj_raw(mlir, len, raw_ir, raw_ir_len, ...)`: the sidecar is parsed
  into the same LLVM context, merged with LLVMLinkModules2, then run through
  always-inline,globaldce via LLVMRunPasses (IRCORE_FLAG_INLINE covers the `.inline`
  marker case, which has no sidecar)
- the worker reads the sidecar itself with stdlib buffers (MemBuddy is not thread-safe),
  so `.ll.pre`, `.ll.link` and `.ll.inline` intermediates are gone as well
- LLVMLinkModules2 is exported by libLLVM-22.dll but no longer declared in the llvm-c
  headers we build against, so it is declared by hand (stable C ABI)
- PhaseB falls back to the legacy flow only for --phase2command now

Verified: RawIRTest runs through ircore with no `.mlir.opt`/`.ll.pre`/`.ll.link`/
`.ll.inline` leftovers and prints the same results as the legacy build; the FFI suite
gained a raw-merge check (the sidecar symbol is undefined without it, linked and inlined
away with it); TestFBProject (also uses __llvmir__) and 12 representative projects pass.
2026-10-30 20:49:13 +08:00
TermiNexus f8368d5e47 build(phaseb): run --traceback through ircore too
--traceback only needed one thing from llc (the per-module part): -frame-pointer=all. The
codegen flags are now a parameter of the FFI instead of a reason to fall back:

- ircore_mlir_to_obj_opts / ircore_llvmir_to_obj_opts take IRCORE_FLAG_* bits:
  ASan instrumentation and frame pointers
- frame pointers are applied by adding the "frame-pointer"="all" function attribute to
  every definition (what clang emits for -fno-omit-frame-pointer; the backend honours it
  and it overrides the target default that llc's flag would have set), after ASan ran so
  its generated helpers are covered too
- PhaseB calls the opts entry point with flags = asan|traceback, so neither mode starts a
  child process anymore; the legacy command flow is unchanged for --phaseb-legacy,
  --phase2command and raw-IR modules

Verified: a --traceback build has no .mlir.opt leftovers, the object carries the same rbp
prologues as `llc -frame-pointer=all`, the <exe>.stackmap is produced (24616 bytes, same
as the legacy build) and the executable runs; --asan still reports the same heap overflow;
12 representative Test projects pass with the deployed compiler.
2026-10-30 20:33:39 +08:00
TermiNexus eba9e9b6cc build(phaseb): run ASan instrumentation in-process too
--asan used to force the legacy PhaseB because the instrumentation step was a separate
`opt -passes=forceattrs,asan -force-attribute=sanitize_address -asan-globals=false` child
process. ircore.dll now exposes ircore_mlir_to_obj_asan / ircore_llvmir_to_obj_asan, which
run the same passes through LLVM's C pass builder (LLVMRunPasses) between MLIR lowering and
codegen, so ASan builds also avoid the three child processes.

- the two cl::opt values are parsed once (std::call_once) before the first pass run
- PhaseB loads the asan entry point together with the others and calls it when _asan_mode
  is set; the legacy `opt` path stays for --phaseb-legacy and is unchanged
- --traceback (llc -frame-pointer=all) and raw-IR modules (llvm-link/opt) still take the
  legacy flow

Verified on a real VKernel module: the ircore-built object and the `opt`-instrumented one
reference the same ASan runtime symbols (69 __asan_report / 4 asan.module_ctor), and a
deliberate heap overflow is reported identically by both builds.
2026-10-30 20:09:00 +08:00
TermiNexus ef4582dcfc build(phaseb): compile modules in-process through ircore.dll
PhaseB spawned three child processes per module (mlir-opt, mlir-translate, llc), and the
process startup dominated the cost: a 0.3 KB module took ~170 ms, of which ~150 ms was
just starting the tools; the 59-module VKernel set cost 18.4 s CPU (3.3 s wall on 16
workers).

ircore.dll (IRCore) does MLIR parse + verify + LLVM IR translation + codegen in a single
call. PhaseB now uses it from the worker threads:

- loaded at runtime with LoadLibraryA/GetProcAddress, not through the import table, so the
  compiler still starts when the DLL is missing; one retry points the loader at clang64's
  bin directory because ircore.dll needs that exact LLVM build
- falls back to the legacy flow automatically (with a warning) when the DLL cannot load
- `--phaseb-legacy` forces the legacy backend; the old command path is kept intact
  (compile_ll_to_obj / init_llc_task / _llc_task_worker / run_llc_parallel)
- ASan instrumentation, raw-IR merging (llvm-link/opt), --traceback frame pointers and
  --phase2command keep using the external tools
- new failure stage LLC_STAGE_IRCORE so a failure is attributed to the FFI call

Measured: 59 VKernel modules 18.4 s -> 4.8 s CPU, 3.3 s -> 1.6 s wall; AstTest
--rebuild-includes 13.6 s -> 11.3 s; BuddyTest 4.8 s -> 2.9 s; ViperC self-host 125 s ->
119 s (PhaseA dominates it); 41 Test projects pass, plus the 5 pre-existing failures.
2026-10-30 18:42:55 +08:00
TermiNexus eda8e4465f refactor(codegen): one body pipeline for module functions and class methods
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.
2026-10-29 19:37:55 +08:00
TermiNexus 3b3cd42d1c fix(codegen): a procedure never emits a value return
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
2026-10-29 18:13:06 +08:00
TermiNexus ffe31cc6e3 fix(call): pass the class segment when resolving a cross-module method return type
The cross-module return-type resolution derived only {sha} and the last segment from {sha}.Class.method, then queried the vpi cache and the dependency text.ll with no class name. Both key methods by class, so a class-level function without a return annotation (Scheduler._yield) missed both lookups and hit the fail-fast 'Cannot obtain the cross-module function return type precisely' path. Derive the middle segment and pass it as the class when it names a registered class; module functions ({sha}.submod.func) keep the bare-name lookup.
2026-10-29 17:48:59 +08:00
TermiNexus f5c5e068ae fix(vpi): keep the bare ExportT declare when another module has the same name
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)
2026-10-29 17:33:31 +08:00
TermiNexus 825fef8f41 fix(vpi): count a static method's first parameter as an argument
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.
2026-10-29 17:19:00 +08:00
TermiNexus 8effc94827 fix(call): bind mod.func() to the module function, not a same-named static method
A suffix lookup for func() also matches {sha}.Class.func symbols. The no-self exception added for sched.Scheduler._yield() was too broad: it kept such a method hit for a module-qualified call like proc.current(), returning Process* where ProcessManager.current() -> int is meant (proc.current() is a module-level function). Keep the namespaced-function exception only when the receiver path itself names the class (mod.Class.func()); bare and module-qualified calls reject method-shaped suffix hits and resolve the module symbol through the vpi.
2026-10-29 17:10:17 +08:00
TermiNexus 1f067aa58f fix(call): accept void methods and rawptr arguments at the call boundary
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.
2026-10-29 16:43:35 +08:00
TermiNexus 4c34e396a4 fix(call): align cross-module calls with the callee symbol's signature
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
2026-10-29 15:53:43 +08:00
TermiNexus a5284f9ce5 fix(codegen): keep float DefineT initializers floating
`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
2026-10-29 02:28:55 +08:00
TermiNexus 52f41c2553 fix(build): invalidate cached include objects when the include set or compiler changes
Include .obj files are cached under <includes>.binary_mlir/<sha>.obj and keyed by the
module's own source sha1 only, while the object body embeds the sha1-prefixed symbols of
every module it calls. After a dependency (or the compiler) changed, the cached object was
still reused, so the link either failed with an undefined symbol for a module sha1 that no
longer existed, or ran code compiled against the previous module graph. Restoring a source
file to a previously built revision therefore surfaced old codegen, which looks like a
compiler regression.

Store a stamp in the cache directory holding the running compiler's build id (exe size and
last write time) plus the sorted sha1 set of every include module; App sources are excluded
so an App-only edit keeps the cache. A mismatch deletes the cached objects and the build
recompiles them from the current sources with the current compiler.
2026-10-29 00:41:05 +08:00
TermiNexus d5d05ec11e fix(codegen): widen from the value expression, not the enclosing statement
should_zext_widen decides the widening direction from the narrow side's AST
node, but the return and assignment coercions passed the statement node.
A statement has no type of its own, so the literal rule and the annotated
variable rule were skipped and the value fell through to sext:
`def f() -> uint64: return 0xDEADBEEF` produced 0xFFFFFFFFDEADBEEF and the
caller compared against 0xDEADBEEF and failed.

Unwrap Return/Assign/AnnAssign/AugAssign/Expr to their value expression
before the direction rules run, so a non-negative literal wider than
INT32_MAX zero-extends (0xDEADBEEF -> 0xDEADBEEF) and a negative one keeps
its sign (-1 -> 0xFFFFFFFFFFFFFFFF).
2026-10-28 23:15:07 +08:00
TermiNexus 19f9c53a5f fix(codegen): zero-extend unsigned 16-bit widening in every coercion path
A uint16 value that is widened to int/uint32/uint64 must be zero-extended
(movzwl), and an int16 value must be sign-extended (movswl). coerce_to_type
picks the direction from the source AST node's declared signedness, which
requires the translator; several call sites omitted it and therefore fell
back to sext, so a big-endian uint16 such as 0xDCBC became 0xFFFF...DCBC
(numGlyphs 56508 read as -9028 and every glyph range collapsed to zero).

- pass trans at the assignment, annotated-assignment, augmented-assignment,
  global-array initializer, index, element-store, constructor-argument,
  conditional-expression and return coercion sites
- printf argument promotion now receives the argument expression and trans,
  so uint16/uint8 promote with zext like any other widening
- print selects the format token and extension from the source signedness
  (%u/%llu + zext for unsigned, %d/%lld + sext for signed) instead of a
  hardcoded %d + sext for 8/16-bit values
2026-10-28 23:06:17 +08:00
TermiNexus 1adc61e3c2 chore: ignore LSP signature cache 2026-10-28 22:36:25 +08:00
TermiNexus 1968748ea5 feat(lsp): cross-file signatures, returns, augassign and richer types
Adds LspSigCache: per-file signature cache under <project>/.viperc-lsp-cache, keyed by content hash+size, invalidated on didChange/didSave; resolves from-import, module-attribute and wildcard calls without building the workspace index. Extends LspTypes with list/array/pointer structural checks, array/string initializer lengths and list-literal element checks; adds return-type consistency, augmented-assignment narrowing, constant folding for unary/binop and string/list subscripts. Diagnostics use a per-line occurrence bucket, a dedicated cache work pool and statement/expression budgets so large include files stay responsive. gate13 covers the new rules.
2026-10-28 22:36:07 +08:00
TermiNexus 79d53e691c fix(codegen): keep module-level DefineT initializers 2026-10-28 21:45:38 +08:00
TermiNexus fde6571a3b feat(lsp): 类型赋值/调用/隐式转换诊断对齐编译器
Adds a conservative type model (LspTypes) and checks for annotated declarations, plain assignments to annotated names, and same-file function calls: implicit narrowing (including the compiler's constant bit-range exemption), bool narrowing, double->float, int<->ptr, float<->ptr, scalar->aggregate, str mismatch, plus argument count/type checks. Signatures are parsed from the existing symbol detail; cross-file calls are intentionally skipped to keep publish latency low. Diagnostics run in a scratch arena sized for analysis, with a 256-diagnostic cap; gate13 covers the new rules and the repo corpus stays clean.
2026-10-28 18:56:14 +08:00
TermiNexus 9ff5224a36 feat(lsp): flag non-void functions that fall off the end
Adds the first semantic rule: a function with a non-void return annotation that can reach the end without return/raise is reported at the return annotation. If/else must both exit; bare return is accepted (compiler parity); ... stubs, extern declarations and .vpi files are skipped. The annotation span is recovered from the header so int*/[]int/const int* are underlined completely. Semantic checks run only on syntactically clean files to avoid cascades. New probe --gate13 covers 13 cases plus a corpus check.
2026-10-28 15:53:13 +08:00
TermiNexus ea53f11027 fix(lsp): honor disablePullDiagnostics and add stale-overflow regression
initializeOptions.disablePullDiagnostics now hides diagnosticProvider and keeps push diagnostics, matching the extension setting. gate2 verifies push and pull both clear the overflow diagnostic after the literal is fixed, and that the disable option falls back to push.
2026-10-28 15:22:01 +08:00
TermiNexus 7072acea82 test(lsp): 覆盖修复字面量后溢出诊断清除
gate2 在 push 与 pull 两条路径上都验证:把超长字面量改成 1 后诊断必须清空,防止词法错误状态串到下一次解析。
2026-10-28 15:19:08 +08:00
TermiNexus 86cced94dc fix(lsp): 拉取诊断客户端不再重复收到 push 诊断
initialize 记录 textDocument.diagnostic 能力;客户端支持 pull 时不再发送 publishDiagnostics,避免 Problems 面板重复。gate2 增加溢出诊断断言:仅 1 条、range 覆盖完整字面量、错误恢复后函数语义 token 仍在,并验证 pull 客户端不会同时收到 push。
2026-10-28 15:07:15 +08:00
TermiNexus 9d298b5ae9 feat(types): 宽度事实表共享给编译器与 LSP
新增 lib/types/IntFacts.vp:显式宽度范围检查、最小可存宽度、最小二幂宽度统一到一张表。ExprGenConst 与 define_min_width 改用它;LSP hover 的位宽推荐/后缀适配判定也改用它,任意 _iN/_uN 后缀可显示,但推荐宽度仍只给二幂值。gate10 增加 128_u8、1_u100、128_u7 与数字分隔符 hover 断言;计划文档记录 Pylance 式诊断等级等决策。
2026-10-28 14:42:25 +08:00
TermiNexus d1f84371f8 feat(lsp): hover 支持字面量、模块路径与位宽分析
新增 LspHover:目标 hover 保留原有签名/文档,模块补充工作区相对路径;光标在数字/字符串字面量上时显示十进制、十六进制、二进制、最小位宽、最小二幂整型宽度(u8..u128 / i8..i128)以及 f16/f32/f64 的精确性;True/False/None 也有 hover。十六进制与十进制改为手写 nibble/逐位输出,避开运行库变参格式化对宽值的问题。gate10 增加模块/整数/hex/bin/浮点/字符串 hover 断言。
2026-10-28 13:37:40 +08:00
TermiNexus 6baad4a6e6 fix(lsp): 别名导入的原始名字也高亮可跳转
\rom mod import name as alias\ 原来只为别名生成 occurrence,原始名 name 没有 token。现在原始名和别名各生成一个 occurrence(按源码顺序),两者都解析到被导入的声明;\import a.b as c\ 仍只标注别名,模块路径保持 namespace。gate10 增加原始名/别名/使用处的 token 与定义断言。
2026-10-28 12:53:44 +08:00
TermiNexus c87f1b6f3c fix(lsp): 通配导入链式重导出可解析
from A import * 的符号没有本地声明,C.b 查 C 的成员失败后原来直接返回;现在沿目标文件的 wildcard 链继续解析(带 hop 上限),模块属性与裸名字两条路径都覆盖。gate10 增加三级通配重导出 token/跳转断言(使用大小写不冲突的夹具名)。
2026-10-28 12:39:28 +08:00
TermiNexus 8e86eea159 fix(codegen): keep unary constant folding within the operand width 2026-10-27 21:33:48 +08:00
TermiNexus e63fc34057 fix(codegen): reject out-of-range field defaults at declaration 2026-10-27 21:23:01 +08:00
TermiNexus 002a98f3f4 fix(codegen): widen method calls by the declared return signedness 2026-10-27 19:56:56 +08:00
TermiNexus 7cbe4e93dc fix(codegen): widen calls by the declared return signedness 2026-10-27 19:51:46 +08:00
TermiNexus 425d3f2120 fix(codegen): widen int() casts by the source signedness 2026-10-27 19:35:56 +08:00
TermiNexus 8215476020 fix(codegen): widen unsigned integers with zext in assignments 2026-10-27 19:27:32 +08:00
TermiNexus ff842e9ff2 fix(lsp): 三元表达式按源码顺序遍历
IfExp 原先先走 test 再走 body,生成的 occurrence 顺序与源码相反,body 的 token 被编码器丢弃或产生负增量。改为 body/test/orelse;gate10 增加三元 body 与 else 的跨模块 token/跳转断言。
2026-10-27 17:54:57 +08:00
TermiNexus b81e4ae3bf fix(lsp): 包的 __init__ 重导出成员可解析
A 是包时 A/__init__.vp 里的 from .leaf import b 被模块链直接跳过:_ChainFromModule 看到 __init__.vp 就切到包目录,只找子模块。改为先在 __init__ 的模块作用域解析该名字(含 from-import 重导出),未命中再下钻子模块。gate10 增加包重导出 token/跳转断言。
2026-10-27 17:43:57 +08:00
TermiNexus 4e3331d442 fix(lsp): else/finally 语句体按语句遍历
If/For/While 的 orelse 和 Try 的 finalbody 是语句列表,却用 _WalkExpr 遍历,else 里的调用基址/属性/名字没有 occurrence,只剩字符串高亮。改用 _WalkBody;gate10 增加多行 else 与单行 else 的跨模块 token/跳转断言。
2026-10-27 17:34:59 +08:00
TermiNexus 4770f57567 fix(lsp): 解码百分号编码盘符后去掉前导分隔符
VS Code 发送 file:///d%3A/...,DecodeUri 只在字面冒号时去前导 /,解出 /d:/... 后 FindProject 失败,项目未采纳、索引 roots 落到工作区根,跨模块成员全部失去高亮与跳转。改为解码完成后判断盘符;gate10 增加百分号编码 rootUri 的回归断言。
2026-10-27 17:19:00 +08:00
TermiNexus e761a85ca7 fix(codegen): split raw IR by envelope length instead of a marker string 2026-10-27 15:07:10 +08:00
TermiNexus 9e47c0ebc4 fix(lsp): as 别名与路径段同名时定位到真实别名
import a.b.c as c 的别名 c 可能已经在模块路径里出现,原先从 alias 起点搜索会命中路径段,真实别名没有 occurrence,语义高亮为白色。asname 存在时从导入名之后开始搜索;gate10 增加 import gate10_pkg.sub as sub 回归断言。
2026-10-27 14:47:34 +08:00
TermiNexus 6a15adbad0 feat(codegen): inline arbitrary LLVM IR and raw MLIR blocks 2026-10-27 14:13:38 +08:00
TermiNexus 645e193729 feat(gen): []T 值类型按初始化器推断定长数组
声明位置(全局/局部/字段)的 []T 依据初始化器推断为 [N x T]:字符串按 strlen+1、列表按元素个数;函数参数仍保持 T* ABI。全局与字段的 .vpi 类型写入推断长度,跨模块声明与定义布局一致;字段默认值支持字符串/列表数组的逐字节/逐元素初始化。V_VPC_VERSION 改为定长数组,使 .PRVERSN 内联真实版本号。
2026-10-27 13:59:28 +08:00
TermiNexus c268e5a9c0 fix(lsp): 别名点号导入逐段高亮
import a.b.c as x 的路径段先于别名写入出现点,避免顺序倒置被丢弃;模块路径段即使解析不到模块也保持 namespace,便于识别缺失依赖。
2026-10-27 13:03:26 +08:00
TermiNexus b38d103c17 fix(lsp): 点号导入逐段高亮与 project includes 路径修复
import a.b.c / from a.b import X 的每个模块段都有 namespace token 并可跳转到对应模块文件;预绑定出现点不再被重解析覆盖;project source/includes 规范化为无 .. 的路径,避免索引路径失配后按需反复解析导致崩溃。
2026-10-27 12:55:43 +08:00
TermiNexus c6b0b03bdc feat(build-self): forward traceback and asan modes to the rebuilt compiler 2026-10-27 12:49:10 +08:00
TermiNexus 8367b5f65b fix(defines): resolve chained define references in vpi stubs 2026-10-27 12:49:00 +08:00
TermiNexus d13eb6b063 feat(lsp): 支持 from module import * 与相对 includes
通配导入的名字通过索引解析(高亮/悬停/跳转/成员),typedef 使用 type token;project.vpj 中任意相对 source/includes 按项目目录解析;修复通配符号表步长导致的越界崩溃;关闭文档释放文档对象,didOpen 不再强制重建索引。
2026-10-27 02:41:45 +08:00