diff --git a/src/abi.h b/src/abi.h index 48eff8f..3e5100c 100644 --- a/src/abi.h +++ b/src/abi.h @@ -59,19 +59,19 @@ struct ReturnABI { // Classify a parameter (mode, type) pair. Pure function of its inputs; // safe to call any number of times. See docs/ABI.md §4. // -// mut → always ByPointer -// let / move, scalar T → ByValue +// mut -> always ByPointer +// let / move, scalar T -> ByValue // let / move, aggregate -// size <= kByValueMaxBytes → ByValue (LLVM handles register packing) -// size > kByValueMaxBytes → ByPointer +// size <= kByValueMaxBytes -> ByValue (LLVM handles register packing) +// size > kByValueMaxBytes -> ByPointer ParamABI classifyParam(ParamMode mode, TypeIdx ty, const JamCodegenContext &ctx); // Classify a return type. See docs/ABI.md §4. // -// scalar T → Direct -// aggregate with size <= 16 B → Direct (LLVM packs into return regs) -// aggregate with size > 16 B → Indirect (sret) +// scalar T -> Direct +// aggregate with size <= 16 B -> Direct (LLVM packs into return regs) +// aggregate with size > 16 B -> Indirect (sret) ReturnABI classifyReturn(TypeIdx ty, const JamCodegenContext &ctx); // Threshold above which an owned aggregate is passed/returned by pointer diff --git a/src/analyzer.cpp b/src/analyzer.cpp index 3aec210..f97e4ef 100644 --- a/src/analyzer.cpp +++ b/src/analyzer.cpp @@ -31,15 +31,15 @@ void Analyzer::pushCycleError(DeclIndex repeated) { std::ostringstream chain; // The cycle is the suffix of analysisStack_ that begins with the // first occurrence of `repeated`. Walk from there to the end so - // the user reads the loop in source order: A → B → A. + // the user reads the loop in source order: A -> B -> A. auto first = std::find(analysisStack_.begin(), analysisStack_.end(), repeated); chain << "dependency loop detected: "; for (auto it = first; it != analysisStack_.end(); ++it) { - if (it != first) chain << " → "; + if (it != first) chain << " -> "; chain << "`" << decls_.get(*it).name << "`"; } - chain << " → `" << repeatedDecl.name << "`"; + chain << " -> `" << repeatedDecl.name << "`"; // The chain itself is in the message, so the reference trace // would just duplicate it. Skip the trace here; other diagnostics // that don't print the chain get it via emitErrorWithRefTrace. @@ -198,7 +198,7 @@ bool Analyzer::resolveTypeFieldsStruct(DeclIndex idx) { // the matching ensure*Body when the field is a *direct* named // type (TypeKind::Named/Struct/Enum/Union, not PtrSingle/ // PtrMany/Slice/Array), so linked-list idioms keep working - // while `S { x: S }` (or struct→union→struct chains) correctly + // while `S { x: S }` (or struct->union->struct chains) correctly // trips the WIP cycle check. const TypeKey &fk = ctx_.getTypePool().get(f.second); if (fk.kind == TypeKind::Named || fk.kind == TypeKind::Struct || @@ -618,7 +618,7 @@ DeclValue Analyzer::analyzeConst(DeclIndex idx) { Decl &d = decls_.get(idx); // `AliasedType` is set by `registerConsts` in main.cpp when it // detects that the const's RHS is a type-resolving expression - // (e.g. `const Box = Vec(i32)` → AliasedType = GenericCall ty). + // (e.g. `const Box = Vec(i32)` -> AliasedType = GenericCall ty). // Reading that field here lets the analyzer surface the alias // as a Type-kind DeclValue, so future call sites that ask the // analyzer "what is X?" get a TypeIdx back instead of None. diff --git a/src/analyzer.h b/src/analyzer.h index 83602fe..2e8587f 100644 --- a/src/analyzer.h +++ b/src/analyzer.h @@ -105,7 +105,7 @@ class Analyzer { DeclValue analyzeFunction(DeclIndex idx); DeclValue analyzeConst(DeclIndex idx); - // Push a "dependency loop detected: A → B → A" diagnostic, using + // Push a "dependency loop detected: A -> B -> A" diagnostic, using // the current analysis stack as the chain. void pushCycleError(DeclIndex repeated); diff --git a/src/ast.h b/src/ast.h index 7979420..6c85acc 100644 --- a/src/ast.h +++ b/src/ast.h @@ -300,7 +300,7 @@ class ModuleAST { // (`__anon_enum_`); the EnumExpr AST node carries the index in // its d.lhs slot. Variant payload types may reference generic // parameters (e.g. `Some(T)`); the substitution engine resolves - // them at each instantiation site (`Option(i32)` → `Some(i32)`). + // them at each instantiation site (`Option(i32)` -> `Some(i32)`). std::vector> AnonEnums; ModuleAST() = default; diff --git a/src/ast_flat.h b/src/ast_flat.h index 5071274..f5c52de 100644 --- a/src/ast_flat.h +++ b/src/ast_flat.h @@ -63,21 +63,22 @@ enum class AstTag : uint8_t { // Calls // d.lhs = StringIdx (callee fully qualified, e.g. "std.fmt.println") - // d.rhs = ExtraIdx → [argCount, arg0, arg1, ...] + // d.rhs = ExtraIdx -> [argCount, arg0, arg1, ...] Call, // Statements Return, // d.lhs = NodeIdx (operand) or kNoNode for bare `return;` Assign, // d.lhs = NodeIdx (target), d.rhs = NodeIdx (value) - // d.lhs = ExtraIdx → [StringIdx name, TypeIdx type, NodeIdx init] + // d.lhs = ExtraIdx -> [StringIdx name, TypeIdx type, NodeIdx init] // d.rhs = flags (bit 0 = isConst) VarDecl, - // d.lhs = NodeIdx (cond), d.rhs = ExtraIdx → + // d.lhs = NodeIdx (cond), d.rhs = ExtraIdx -> // [thenCount, elseCount, then0, then1, ..., else0, else1, ...] IfNode, - // d.lhs = NodeIdx (cond), d.rhs = ExtraIdx → [bodyCount, body0, body1, ...] + // d.lhs = NodeIdx (cond), d.rhs = ExtraIdx -> [bodyCount, body0, body1, + // ...] WhileNode, - // d.lhs = ExtraIdx → [StringIdx var, NodeIdx start, NodeIdx end, + // d.lhs = ExtraIdx -> [StringIdx var, NodeIdx start, NodeIdx end, // bodyCount, body0, body1, ...] ForNode, Break, @@ -86,17 +87,17 @@ enum class AstTag : uint8_t { // Module-level ImportLit, // d.lhs = StringIdx (module path) // d.lhs = TypeIdx (struct type, kNoType if inferred from var-decl context) - // d.rhs = ExtraIdx → [fieldCount, fieldName0, fieldExpr0, fieldName1, ...] + // d.rhs = ExtraIdx -> [fieldCount, fieldName0, fieldExpr0, fieldName1, ...] StructLit, // `[a, b, c, ...]` array literal in expression position. Element type // inferred from var-decl target type (kNoType if unknown — codegen // rejects). // d.lhs = TypeIdx (target element type, kNoType if unbound) - // d.rhs = ExtraIdx → [count, elem0, elem1, ...] + // d.rhs = ExtraIdx -> [count, elem0, elem1, ...] ArrayLit, // `[expr; N]` array repeat literal. N is a constant integer expression. // d.lhs = TypeIdx (target array type, kNoType if unbound) - // d.rhs = ExtraIdx → [valueNode, countNode] + // d.rhs = ExtraIdx -> [valueNode, countNode] ArrayRepeat, // a `struct { fields, methods }` expression, evaluated at // compile time to a value of type `type`. The expression's body lives @@ -119,7 +120,7 @@ enum class AstTag : uint8_t { // Pattern match (integer literals, ranges, or-patterns, wildcard). // The catch-all is the wildcard pattern `_`; there is no `else` arm. // d.lhs = NodeIdx (scrutinee expression) - // d.rhs = ExtraIdx → [armCount, + // d.rhs = ExtraIdx -> [armCount, // arm0_patIdx, arm0_bodyCount, arm0_body..., // arm1_patIdx, arm1_bodyCount, arm1_body..., ...] MatchNode, @@ -136,7 +137,7 @@ enum class AstTag : uint8_t { // Two arg-encoding shapes, discriminated by d.flags bit 0: // * flags bit 0 = 0 — type-arg single-form. d.rhs = TypeIdx. // Used by `@sizeOf(T)`, `@alignOf(T)`. - // * flags bit 0 = 1 — expr-arg multi-form. d.rhs = ExtraIdx → + // * flags bit 0 = 1 — expr-arg multi-form. d.rhs = ExtraIdx -> // [argCount, arg0_NodeIdx, arg1_NodeIdx, ...]. Used by // `@emit*` intrinsics callable from cfn bodies. // @@ -144,16 +145,16 @@ enum class AstTag : uint8_t { AtCall, // Static method call on a generic-call type receiver: - // Vec(i32).empty() → struct static method - // Option(i32).Some(42) → enum variant constructor - // Option(i32).None() → unit-variant constructor + // Vec(i32).empty() -> struct static method + // Option(i32).Some(42) -> enum variant constructor + // Option(i32).None() -> unit-variant constructor // The parser detects the `IDENT(args).IDENT(args)` shape via // paren-balanced peek-ahead, parses the inner args as TYPES, and // builds a GenericCall TypeIdx for the receiver. Codegen resolves // the receiver type (instantiating if necessary) and dispatches // to the appropriate static-method or variant-constructor path. // d.lhs = TypeIdx (receiver type — typically GenericCall) - // d.rhs = ExtraIdx → [methodNameId, argCount, arg0, arg1, ...] + // d.rhs = ExtraIdx -> [methodNameId, argCount, arg0, arg1, ...] TypeMethodCall, // Pattern atoms — internal nodes used inside MatchNode arms. Never @@ -166,7 +167,7 @@ enum class AstTag : uint8_t { PatRange, // No payload. PatWildcard, - // d.lhs = ExtraIdx → [count, sub0, sub1, ...]; each subN is a NodeIdx + // d.lhs = ExtraIdx -> [count, sub0, sub1, ...]; each subN is a NodeIdx // of a PatLit / PatRange / PatWildcard / PatEnumVariant. PatOr, // Enum-variant pattern (`EnumName.VariantName` in a match arm). diff --git a/src/astgen.cpp b/src/astgen.cpp index 07e8612..aaeadb2 100644 --- a/src/astgen.cpp +++ b/src/astgen.cpp @@ -323,7 +323,7 @@ static JirRef emitCall(AstGenCtx &gctx, const FunctionAST *fn, const std::vector &argRefs); // `v[i]` desugar dispatch — see `emitStructCfnDispatch` for the body. // Forward-declared so astgenAssign (in this file, above the -// definition) can call it for `v[i] = x` → setAt routing. +// definition) can call it for `v[i] = x` -> setAt routing. static JirRef emitStructCfnDispatch(AstGenCtx &gctx, const JamCodegenContext::StructInfo *sinfo, const char *methodName, JirRef recv, @@ -407,7 +407,7 @@ static JirRef astgenNumberLit(AstGenCtx &gctx, const AstNode &n, // directly so literals lower at the consumer's width (this is the // peer-type-propagation contract). When the expected type is a // generic call or alias that resolves to an integer (e.g. - // `Identity(i32)` → i32), resolve it first so the contract holds + // `Identity(i32)` -> i32), resolve it first so the contract holds // transitively. Otherwise fall back to the smallest-fit width. if (expected != kNoType) { TypeIdx resolved = expected; @@ -561,7 +561,7 @@ static void emitDrops(AstGenCtx &gctx, const std::vector &bindings) { } // AstGen for `VarDecl`. Lowers to Alloca + Store(init). The variable's -// name → alloca ref binding is recorded so subsequent reads emit +// name -> alloca ref binding is recorded so subsequent reads emit // Load(alloca). static void astgenVarDecl(AstGenCtx &gctx, const AstNode &n) { const NodeStore &ns = gctx.ctx.getNodeStore(); @@ -887,7 +887,7 @@ static void astgenAssign(AstGenCtx &gctx, const AstNode &n) { NodeIdx targetIdx = static_cast(n.lhs); NodeIdx valueIdx = static_cast(n.rhs); - // `v[i] = x` on a struct → `v.setAt(i, x)`. + // `v[i] = x` on a struct -> `v.setAt(i, x)`. const AstNode &target = gctx.ctx.getNodeStore().get(targetIdx); if (target.tag == AstTag::Index) { NodeIdx baseIdx = static_cast(target.lhs); @@ -1040,7 +1040,7 @@ static JirRef astgenStructLit(AstGenCtx &gctx, const AstNode &n, } TypeIdx expectedField = info->fields[idx].second; JirRef fieldVal = astgenExpr(gctx, exprIdx, expectedField); - // Silent int→float widening matches the legacy struct codegen: + // Silent int->float widening matches the legacy struct codegen: // `Vec3 { x: 0 }` with `x: f32` lands an integer literal here; // emit SIToFP / UIToFP to settle the IR type instead of letting // jir_codegen pipe an integer value into a float slot of the @@ -1133,6 +1133,66 @@ static JirRef astgenMemberAccess(AstGenCtx &gctx, const AstNode &n) { } } + // Addressable base -> FieldAddr + Load fieldTy. The value path + // below would emit Load wholeStruct + ExtractValue, which for a + // struct embedding large arrays scales as O(structSize) per field + // read — a method reading N fields from a 1KB self struct emitted + // N copies of a 1KB load. + bool baseIsLvalueable = baseNode.tag == AstTag::Variable || + baseNode.tag == AstTag::MemberAccess || + baseNode.tag == AstTag::Index || + baseNode.tag == AstTag::Deref; + if (baseIsLvalueable) { + TypeIdx baseLeafTy = kNoType; + JirRef basePtr = astgenLvalue(gctx, baseIdx, baseLeafTy); + if (const auto *uinfo = gctx.ctx.lookupUnion(baseLeafTy)) { + TypeIdx fieldTy = gctx.ctx.getUnionFieldType(uinfo->name, member); + if (fieldTy == kNoType) { + failHere(gctx, "astgen: union `" + uinfo->name + + "` has no field `" + member + "`"); + } + TypeIdx fieldPtrTy = gctx.ctx.getTypePool().intern( + TypeKey{TypeKind::PtrSingle, 0, 0, fieldTy, 0}); + JirInst bc{}; + bc.tag = JirTag::BitCast; + bc.a = basePtr; + bc.ty = fieldPtrTy; + JirRef fp = emit(gctx, bc); + JirInst ld{}; + ld.tag = JirTag::Load; + ld.a = fp; + ld.ty = fieldTy; + return emit(gctx, ld); + } + if (const auto *info = gctx.ctx.lookupStruct(baseLeafTy)) { + int idx = gctx.ctx.getFieldIndex(info->name, member); + if (idx < 0) { + return recoverHere(gctx, + "unknown field `" + member + "` on `" + + info->name + "`", + kNoType); + } + TypeIdx fieldTy = info->fields[idx].second; + TypeIdx fieldPtrTy = gctx.ctx.getTypePool().intern( + TypeKey{TypeKind::PtrSingle, 0, 0, fieldTy, 0}); + JirInst fa{}; + fa.tag = JirTag::FieldAddr; + fa.a = basePtr; + fa.b = static_cast(idx); + fa.ty = fieldPtrTy; + JirRef fp = emit(gctx, fa); + JirInst ld{}; + ld.tag = JirTag::Load; + ld.a = fp; + ld.ty = fieldTy; + return emit(gctx, ld); + } + // Slice base (.ptr / .len), or some other shape — fall + // through to the value path. The astgenLvalue call above + // emitted an alloca/GEP lookup that may go unused; small + // cost relative to the win for struct/union bases. + } + // Regular struct field projection. JirRef baseRef = astgenExpr(gctx, baseIdx, kNoType); TypeIdx baseTy = gctx.jfn.getInst(baseRef).ty; @@ -1356,9 +1416,9 @@ static JirRef emitStructCfnDispatch(AstGenCtx &gctx, // Statically determine, without emitting any JIR, whether `node` is // an addressable expression chain whose leaf type is Array — and if // so, return that Array TypeIdx (else kNoType). Walks the AST -// structurally: Variable → localTypes; MemberAccess → recurse on the +// structurally: Variable -> localTypes; MemberAccess -> recurse on the // parent, then look up the field type via the parent's StructInfo; -// Index → recurse on base, then unwrap one Array dimension; Deref → +// Index -> recurse on base, then unwrap one Array dimension; Deref -> // recurse on the pointee. Anything else (slice projections, calls, // arithmetic, literals) returns kNoType. // @@ -1493,7 +1553,7 @@ static JirRef astgenIndex(AstGenCtx &gctx, const AstNode &n) { TypeIdx baseTy = gctx.jfn.getInst(baseRef).ty; const TypeKey &k = gctx.ctx.getTypePool().get(baseTy); - // Struct dispatch: `v[i]` → `v.at(i)`. The `at` method is value- + // Struct dispatch: `v[i]` -> `v.at(i)`. The `at` method is value- // shaped — it returns T directly, no pointer wrapper. The call's // result IS the index expression's value; nothing more to do. // `lookupStruct` chases Struct / Named / GenericCall TypeKinds @@ -1632,7 +1692,7 @@ static JirRef astgenDeref(AstGenCtx &gctx, const AstNode &n) { static JirRef astgenAsCast(AstGenCtx &gctx, const AstNode &n) { NodeIdx operandIdx = static_cast(n.lhs); TypeIdx dstTyOrig = static_cast(n.rhs); - // Resolve GenericCall destinations (e.g. `Identity(u8)` → u8) so + // Resolve GenericCall destinations (e.g. `Identity(u8)` -> u8) so // the downstream Int/Float branches see the concrete type. TypeIdx dstTy = dstTyOrig; { @@ -1666,7 +1726,7 @@ static JirRef astgenAsCast(AstGenCtx &gctx, const AstNode &n) { return emit(gctx, inst); }; - // Bool-to-integer: i1 → integer of any width. + // Bool-to-integer: i1 -> integer of any width. if (src.kind == TypeKind::Bool && dst.kind == TypeKind::Int) { JirInst inst{}; inst.tag = JirTag::ZExt; @@ -1803,9 +1863,9 @@ static JirRef astgenAsCast(AstGenCtx &gctx, const AstNode &n) { } // AstGen for `UnaryOp`. Three forms: -// `-x` (Neg) → 0 - x for ints, FNeg for floats -// `!x` (LogNot) → x == false (xor 1) -// `~x` (BitNot) → x XOR all-ones +// `-x` (Neg) -> 0 - x for ints, FNeg for floats +// `!x` (LogNot) -> x == false (xor 1) +// `~x` (BitNot) -> x XOR all-ones static JirRef astgenUnaryOp(AstGenCtx &gctx, const AstNode &n, TypeIdx expected) { NodeIdx opIdx = static_cast(n.lhs); @@ -2414,8 +2474,8 @@ struct SwitchCase { // caller — it doesn't contribute cases, it becomes the default block. // // `scrutIsEnum` selects how PatLit / PatEnumVariant are validated: -// * integer scrutinee → only PatLit accepted; value = literal bits. -// * enum scrutinee → only PatEnumVariant accepted; value = the +// * integer scrutinee -> only PatLit accepted; value = literal bits. +// * enum scrutinee -> only PatEnumVariant accepted; value = the // variant's discriminant byte. The enum's // `EnumInfo` is supplied so we can resolve the // variant name without re-doing the lookup @@ -2553,7 +2613,7 @@ static void astgenPatternCompare(AstGenCtx &gctx, NodeIdx patIdx, JirRef scrut, case AstTag::PatEnumVariant: { // Decode the PatEnumVariant via the four-way encoding documented // in the legacy `decodePatEnumVariant`: - // flags & 1: bindings present, lhs = ExtraIdx → [recv, variant, + // flags & 1: bindings present, lhs = ExtraIdx -> [recv, variant, // count, name0, name1, ...]; else lhs is the receiver // flags & 2: receiver is a TypeIdx (GenericCall, resolved via // lookupEnum); else it's a StringIdx @@ -2968,7 +3028,7 @@ static JirRef astgenMatch(AstGenCtx &gctx, const AstNode &n, TypeIdx expected) { // Resolves the receiver TypeIdx to its struct/enum name (triggering // lazy generic instantiation as a side effect), synthesizes a regular // Call AST node with qualified name `ReceiverName.method`, and recurses -// into astgenCall. Instantiation runs the full astgen → jirDefineBody +// into astgenCall. Instantiation runs the full astgen -> jirDefineBody // pipeline in `JamCodegenContext::instantiateStructExpr`, so the JIR // Call here resolves cleanly by LLVM name at codegen time. static JirRef astgenTypeMethodCall(AstGenCtx &gctx, const AstNode &n) { @@ -3334,30 +3394,28 @@ static JirRef lowerArg(AstGenCtx &gctx, NodeIdx argIdx, const Param &p) { if (pabi.kind != jam::abi::ParamABI::Kind::ByPointer) { return astgenExpr(gctx, argIdx, p.Type); } - // ByPointer: feed an address. Mut / Move want the caller's - // storage when the arg is an lvalue (so writes are observed by the - // caller). Let / Const reach here only for large aggregates — the - // callee can't mutate them through the pointer (no `mut`), but the - // LLVM signature wants a pointer because the value is too big to - // pass in registers. In both cases, when the arg isn't already an - // lvalue we spill it to a fresh alloca. + // ByPointer: feed an address. Lvalueable arg -> hand the existing + // storage ptr; non-lvalue rvalue -> spill to a fresh alloca. All + // four param modes share this shape — mut/move want the caller's + // storage so writes are observed, let on large aggregates wants + // it because the value is too big to pass in registers. Routing + // let/move-by-ptr through the lvalue path saves the otherwise- + // dead value-load + spill at every call site. const AstNode &argNode = gctx.ctx.getNodeStore().get(argIdx); - if (p.Mode == ParamMode::Mut || p.Mode == ParamMode::Move) { - TypeIdx leafTy = kNoType; - switch (argNode.tag) { - case AstTag::Variable: - case AstTag::MemberAccess: - case AstTag::Index: - case AstTag::Deref: - return astgenLvalue(gctx, argIdx, leafTy); - case AstTag::AddressOf: - return astgenExpr(gctx, argIdx, kNoType); - default: - break; - } + TypeIdx leafTy = kNoType; + switch (argNode.tag) { + case AstTag::Variable: + case AstTag::MemberAccess: + case AstTag::Index: + case AstTag::Deref: + return astgenLvalue(gctx, argIdx, leafTy); + case AstTag::AddressOf: + return astgenExpr(gctx, argIdx, kNoType); + default: + break; } JirRef val = astgenExpr(gctx, argIdx, p.Type); - TypeIdx leafTy = gctx.jfn.getInst(val).ty; + leafTy = gctx.jfn.getInst(val).ty; JirInst alloca{}; alloca.tag = JirTag::Alloca; alloca.ty = leafTy; @@ -4036,8 +4094,21 @@ static JirRef astgenCall(AstGenCtx &gctx, const AstNode &n) { JirRef basePtr = astgenExpr(gctx, recvExprIdx, kNoType, ResultLoc::Pointer); TypeIdx leafTy = gctx.jfn.getInst(basePtr).ty; - const TypeKey &lk = gctx.ctx.getTypePool().get(leafTy); - if (lk.kind == TypeKind::Array) { + const TypeKey &lk0 = gctx.ctx.getTypePool().get(leafTy); + // MemberAccess on a struct field returns a FieldAddr whose + // type is `PtrSingle(arrayTy)` (the field's storage + // address), not the array itself. Unwrap one pointer level + // so the Array check fires for `self.field.asMutPtr()` the + // same way it does for `arr.asMutPtr()` on a local. Same + // goes for `PtrMany` from an Index lvalue. + TypeKind lkKind = lk0.kind; + TypeIdx unwrappedTy = leafTy; + if (lkKind == TypeKind::PtrSingle || lkKind == TypeKind::PtrMany) { + unwrappedTy = static_cast(lk0.a); + lkKind = gctx.ctx.getTypePool().get(unwrappedTy).kind; + } + const TypeKey &lk = gctx.ctx.getTypePool().get(unwrappedTy); + if (lkKind == TypeKind::Array) { TypeIdx elemTy = static_cast(lk.a); TypeIdx ptrTy = gctx.ctx.getTypePool().intern( TypeKey{TypeKind::PtrMany, 0, 0, elemTy, 0}); @@ -4059,19 +4130,36 @@ static JirRef astgenCall(AstGenCtx &gctx, const AstNode &n) { // below. (basePtr is unused; cheap, no Load was emitted.) } - // Resolve the receiver as a value for struct / enum dispatch. - // For mut/move dispatch we'll redo this as an lvalue below — - // passing the original storage pointer instead of a spilled - // copy. - JirRef recvVal = astgenExpr(gctx, recvExprIdx, kNoType); - TypeIdx recvTy = gctx.jfn.getInst(recvVal).ty; - { + // Receiver lowering. The goal is to never emit a full- + // aggregate value-load just to peek the receiver's type when + // `outer.field.method()` embeds a large struct. + // * lvalueable recv (Variable / MemberAccess / Index / + // Deref): astgenLvalue gives the pointer and the receiver + // type with no value-load. mut/move methods take the ptr + // straight; by-value methods Load through it. + // * non-lvalueable recv (Call result, etc.): value path. + // by-value methods consume the value directly; mut/move + // methods spill to a fresh alloca. + JirRef recvVal = kNoJirRef; + JirRef recvLvaluePtr = kNoJirRef; + TypeIdx recvTy = kNoType; + const AstNode &recvNodeForLvalue = ns.get(recvExprIdx); + bool recvLvalueable = recvNodeForLvalue.tag == AstTag::Variable || + recvNodeForLvalue.tag == AstTag::MemberAccess || + recvNodeForLvalue.tag == AstTag::Index || + recvNodeForLvalue.tag == AstTag::Deref; + if (recvLvalueable) { + TypeIdx leafTy = kNoType; + recvLvaluePtr = astgenLvalue(gctx, recvExprIdx, leafTy); + recvTy = leafTy; + } else { + recvVal = astgenExpr(gctx, recvExprIdx, kNoType); + recvTy = gctx.jfn.getInst(recvVal).ty; const TypeKey &recvKey = gctx.ctx.getTypePool().get(recvTy); if (recvKey.kind == TypeKind::Array) { // Non-lvalueable array receiver (e.g. a call result): - // the rvalue-Load path got us here. Without a stable - // storage location, we can't safely hand a pointer - // to FFI — reject. + // without a stable storage location we can't safely + // hand a pointer to FFI — reject. failHere(gctx, "astgen: `" + methodName + "()` requires an addressable array " "(variable, field, or indexed slot)"); @@ -4097,35 +4185,39 @@ static JirRef astgenCall(AstGenCtx &gctx, const AstNode &n) { } ParamMode mode = method->Args.empty() ? ParamMode::Let : method->Args[0].Mode; - JirRef recvArg = recvVal; + JirRef recvArg; if (mode == ParamMode::Mut || mode == ParamMode::Move) { - // Re-lower the receiver as an lvalue so the method sees - // the original storage. For non-lvalue rvalues we spill - // the already-computed value to a fresh alloca. - const AstNode &recvNode = ns.get(recvExprIdx); - TypeIdx leafTy = kNoType; - JirRef ptr; - switch (recvNode.tag) { - case AstTag::Variable: - case AstTag::MemberAccess: - case AstTag::Index: - case AstTag::Deref: - ptr = astgenLvalue(gctx, recvExprIdx, leafTy); - break; - default: { + if (recvLvaluePtr != kNoJirRef) { + recvArg = recvLvaluePtr; + } else { + // Non-lvalue receiver with a mut/move method: spill the + // already-computed value to a fresh alloca so the + // callee gets a stable storage address. JirInst alloca{}; alloca.tag = JirTag::Alloca; alloca.ty = recvTy; - ptr = emitAllocaHoisted(gctx, alloca); + JirRef ptr = emitAllocaHoisted(gctx, alloca); JirInst store{}; store.tag = JirTag::Store; store.a = ptr; store.b = recvVal; emit(gctx, store); - break; + recvArg = ptr; } + } else { + if (recvVal != kNoJirRef) { + recvArg = recvVal; + } else { + // Lvalue receiver + by-value method: Load through the + // ptr now. This is the only spot where a full-aggregate + // load is unavoidable; the previous design emitted it + // unconditionally even when the method took a pointer. + JirInst load{}; + load.tag = JirTag::Load; + load.a = recvLvaluePtr; + load.ty = recvTy; + recvArg = emit(gctx, load); } - recvArg = ptr; } std::vector argRefs; argRefs.reserve(1 + argCount); @@ -4297,7 +4389,7 @@ static JirRef astgenCall(AstGenCtx &gctx, const AstNode &n) { offC.ty = BuiltinType::U64; JirRef offRef = emit(gctx, offC); gepInst.b = offRef; - gepInst.ty = payAreaPtrTy; // *u8 → byte stride + gepInst.ty = payAreaPtrTy; // *u8 -> byte stride JirRef fieldPtr = emit(gctx, gepInst); JirInst payStore{}; payStore.tag = JirTag::Store; diff --git a/src/codegen.cpp b/src/codegen.cpp index 9b0c562..58f8f36 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -407,7 +407,7 @@ JamCodegenContext::lookupEnum(TypeIdx ty) const { const TypeKey &k = typePool.get(ty); // a GenericCall TypeIdx resolves to a concrete type; // recurse on the resolved TypeIdx so generic enum instantiations - // (e.g. `Option(i32)` → `Option__i32`) resolve uniformly. + // (e.g. `Option(i32)` -> `Option__i32`) resolve uniformly. if (k.kind == TypeKind::GenericCall) { return lookupEnum(resolveGenericCall(ty)); } @@ -621,7 +621,7 @@ uint64_t JamCodegenContext::typeSize(TypeIdx ty) const { case TypeKind::Struct: case TypeKind::Named: { // a Named type may be a substitution-context - // reference to a parameter (T → i32) or to Self. Resolve + // reference to a parameter (T -> i32) or to Self. Resolve // through the substitution map first; if found and the // target is a primitive (Int/Float/etc.), the recursive // typeSize handles it. Same shape as getLLVMType. @@ -742,7 +742,7 @@ uint64_t JamCodegenContext::typeAlign(TypeIdx ty) const { case TypeKind::Struct: case TypeKind::Named: { // substitution context wins. A Named type may - // be a parameter reference (T → i32) or Self that resolves + // be a parameter reference (T -> i32) or Self that resolves // to a non-aggregate; the recursive call handles primitives. const std::string &substName = stringPool.get(static_cast(k.a)); @@ -856,7 +856,7 @@ TypeIdx substituteType(TypeIdx ty, case TypeKind::Fn: { // Function-typed value: substitute the return type AND every // param type. A `fn(T) T` field inside a generic struct sees - // T → concrete on instantiation, so the field's TypeIdx must + // T -> concrete on instantiation, so the field's TypeIdx must // rebuild with substituted children. TypeIdx retSub = substituteType(static_cast(k.a), subst, types, strings); @@ -1043,7 +1043,7 @@ TypeIdx JamCodegenContext::instantiateStructExpr( const StructDeclAST *anon = (*anonStructs_)[anonIdx].get(); // Build the instantiated struct's name from the callee + arg names. - // `Maybe(File)` → `Maybe__File`. Pointer/array types lower through + // `Maybe(File)` -> `Maybe__File`. Pointer/array types lower through // substituteType; we only need a stable spelling for the canonical // non-compound cases here. v1's stdlib won't pass non-named types // as generic args, so this is enough to get the demo running. @@ -1078,9 +1078,9 @@ TypeIdx JamCodegenContext::instantiateStructExpr( return typePool.internNamed(stringPool.intern(instName)); } - // Build the full substitution map: parameter names → concrete args, + // Build the full substitution map: parameter names -> concrete args, // plus the anon-struct's synthetic name (which is what `Self` - // resolved to in *type* positions at parse time) → the new + // resolved to in *type* positions at parse time) -> the new // instantiated struct's Named TypeIdx. We also alias the literal // string "Self" to the same target so codegen sites that see // the parser's stringified `Self.method(...)` (an expression- @@ -1170,7 +1170,7 @@ TypeIdx JamCodegenContext::instantiateStructExpr( // nested type expressions in the signature. setCurrentSubst(bodySubst); // Pass 1 builds a signature-only JirFunction and emits - // the prototype with JIR's ABI (mut/move → ptr). The + // the prototype with JIR's ABI (mut/move -> ptr). The // metadata is cached on the InstMethod entry so Pass 2 // can continue from here instead of rebuilding it. JirFunction passOneJir = astgenMetadata(*clonePtr, mutCtx); @@ -1286,7 +1286,7 @@ TypeIdx JamCodegenContext::instantiateEnumExpr( } // Memoize. Return as a Named TypeIdx so the rest of codegen resolves - // through the existing Named → EnumInfo path (handles size/align, + // through the existing Named -> EnumInfo path (handles size/align, // match dispatch, etc., uniformly with non-generic enum references). if (const EnumInfo *existing = getEnum(instName)) { (void)existing; diff --git a/src/codegen.h b/src/codegen.h index 954406a..567f3d3 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -243,7 +243,7 @@ class JamCodegenContext { // Lazy LLVM type per TypeIdx (built once, reused). Indexed by TypeIdx. mutable std::vector llvmTypeCache; - // Pre-built map of struct → drop fn (built before any function is + // Pre-built map of struct -> drop fn (built before any function is // lowered). AstGen consults this to wire drop calls at scope exit; // drop tracking itself lives in the per-function `AstGenCtx`. const jam::drops::DropRegistry *dropRegistry = nullptr; @@ -302,7 +302,7 @@ class JamCodegenContext { // `pub struct`, `pub enum`, `pub union`, `pub const Foo = T` // by source-level name. TypeIdx points into the global TypePool. std::unordered_map types; - // `pub const X = import(...)`-style re-exports: name → the + // `pub const X = import(...)`-style re-exports: name -> the // Module TypeIdx for the re-exported module. std::unordered_map moduleAliases; }; @@ -313,7 +313,7 @@ class JamCodegenContext { // graph. Used as a fallback when the flat // `getFunctionAST("handle.X")` lookup misses on a 3+ segment path // like `std.fmt.print`. Returns nullptr if any segment fails to - // resolve to a Module → Module → ... → Function chain. + // resolve to a Module -> Module -> ... -> Function chain. const FunctionAST *resolveChainedFunction(const std::string &dotted) const; // Sibling of `resolveChainedFunction` for types. A type annotation @@ -329,7 +329,7 @@ class JamCodegenContext { private: std::unordered_map functionAsts; std::unordered_map importHandles_; - // Resolved canonical path → namespace decl table. See ModuleNamespace. + // Resolved canonical path -> namespace decl table. See ModuleNamespace. std::unordered_map moduleNamespaces_; // `genericResolutions_` memoizes per-callsite: every unique @@ -375,7 +375,7 @@ class JamCodegenContext { const std::vector> *anonEnums_ = nullptr; // type alias table. `const BoxI32 = Box(i32);` registers - // `BoxI32 → resolved-TypeIdx-of-Box(i32)`. Consulted by lookupStruct + // `BoxI32 -> resolved-TypeIdx-of-Box(i32)`. Consulted by lookupStruct // (and by getLLVMType via the recursive lookup path) so a binding // declared `var b: BoxI32` finds the same struct that `Box(i32)` // would produce. diff --git a/src/comptime.cpp b/src/comptime.cpp index c4c5ae4..af056f7 100644 --- a/src/comptime.cpp +++ b/src/comptime.cpp @@ -162,7 +162,7 @@ ComptimeValue ComptimeEvaluator::evalAtCall(const AstNode &n, // return None (caller can do its own dispatch if it wants). if ((n.flags & 1) == 0) { return ComptimeValue::makeNone(); } - // Expr-arg multi-form: rhs = ExtraIdx → [argCount, arg0, ...]. + // Expr-arg multi-form: rhs = ExtraIdx -> [argCount, arg0, ...]. ExtraIdx extra = static_cast(n.rhs); uint32_t argCount = nodes_.getExtra(extra); std::vector argVals; diff --git a/src/comptime.h b/src/comptime.h index feeff91..8a02657 100644 --- a/src/comptime.h +++ b/src/comptime.h @@ -89,7 +89,7 @@ struct ComptimeValue { uint64_t asU64() const; }; -// A lexically-scoped map of name → ComptimeValue. The evaluator reads +// A lexically-scoped map of name -> ComptimeValue. The evaluator reads // from it when it encounters a Variable node, and the executor mutates // it on var-decl / assignment statements. // diff --git a/src/decl.h b/src/decl.h index 2e81118..b0575d7 100644 --- a/src/decl.h +++ b/src/decl.h @@ -78,7 +78,7 @@ enum class StructStatus : uint8_t { // Enum / Union body lifecycles. Same shape as StructStatus but kept // in their own enums so a tagged-union variant's status can't be -// silently treated as a struct's. Both follow None → BodyWIP → +// silently treated as a struct's. Both follow None -> BodyWIP -> // HaveBody; the WIP state is the cycle-detector key for // `resolveTypeFieldsEnum` / `resolveTypeFieldsUnion`. enum class EnumStatus : uint8_t { diff --git a/src/diagnostics.h b/src/diagnostics.h index 83c124e..34f194a 100644 --- a/src/diagnostics.h +++ b/src/diagnostics.h @@ -74,7 +74,7 @@ struct Diagnostic { // reaches the diagnostic emitter with the chain attached. // // Scope: this is a stack of *currently-active* instantiation frames. -// It captures nested instantiation (A→B→C while all three are still +// It captures nested instantiation (A->B->C while all three are still // in flight) but does NOT capture references from already-completed // decls. The stack covers the practical case where instantiation // errors fire mid-stack; cross-decl historical reference chains are diff --git a/src/drop_registry.h b/src/drop_registry.h index c22ed35..15aad7e 100644 --- a/src/drop_registry.h +++ b/src/drop_registry.h @@ -21,7 +21,7 @@ class StringPool; namespace jam { namespace drops { -// Map of struct name → user-defined drop function for that type. +// Map of struct name -> user-defined drop function for that type. // // A type T is "drop-bearing" when the program declares // diff --git a/src/init_analysis.cpp b/src/init_analysis.cpp index af5eb3a..8dd18d1 100644 --- a/src/init_analysis.cpp +++ b/src/init_analysis.cpp @@ -106,7 +106,7 @@ class Analyzer { struct BorrowPath { StringIdx base = kNoString; // base binding name, or kNoString if // the arg isn't a simple lvalue chain - std::vector steps; // root → leaf order after extraction + std::vector steps; // root -> leaf order after extraction }; BorrowPath extractPath(NodeIdx argIdx) const; @@ -146,7 +146,7 @@ std::vector Analyzer::run(const FunctionAST &fn) { NameMap state; // parameter entry state depends on the declared mode. - // Let / Mut / Move → Init (caller's binding is valid) + // Let / Mut / Move -> Init (caller's binding is valid) // `move` does not change anything for the callee's view of its own // parameter — the moved-from-ness applies to the *caller's* binding // after the call. @@ -320,7 +320,7 @@ Result Analyzer::analyze(NodeIdx idx, NameMap state) { // Statement / declaration cases. Result Analyzer::analyzeVarDecl(NodeIdx idx, NameMap state) { - // VarDecl: d.lhs = ExtraIdx → [StringIdx name, TypeIdx type, NodeIdx init] + // VarDecl: d.lhs = ExtraIdx -> [StringIdx name, TypeIdx type, NodeIdx init] const AstNode &n = nodes_.get(idx); ExtraIdx extra = n.lhs; StringIdx nameIdx = nodes_.getExtra(extra + 0); @@ -391,7 +391,8 @@ Result Analyzer::analyzeAssignTarget(NodeIdx idx, NameMap state) { Result Analyzer::analyzeIf(NodeIdx idx, NameMap state) { // IfNode: d.lhs = NodeIdx (cond) - // d.rhs = ExtraIdx → [thenCount, elseCount, then0, ..., else0, ...] + // d.rhs = ExtraIdx -> [thenCount, elseCount, then0, ..., else0, + // ...] const AstNode &n = nodes_.get(idx); auto r = analyze(n.lhs, std::move(state)); if (r.terminated) return r; @@ -426,7 +427,7 @@ Result Analyzer::analyzeIf(NodeIdx idx, NameMap state) { } Result Analyzer::analyzeWhile(NodeIdx idx, NameMap state) { - // WhileNode: d.lhs = NodeIdx (cond), d.rhs = ExtraIdx → + // WhileNode: d.lhs = NodeIdx (cond), d.rhs = ExtraIdx -> // [bodyCount, body0, body1, ...] const AstNode &n = nodes_.get(idx); auto r = analyze(n.lhs, std::move(state)); @@ -452,7 +453,7 @@ Result Analyzer::analyzeWhile(NodeIdx idx, NameMap state) { } Result Analyzer::analyzeFor(NodeIdx idx, NameMap state) { - // ForNode: d.lhs = ExtraIdx → [StringIdx var, NodeIdx start, NodeIdx end, + // ForNode: d.lhs = ExtraIdx -> [StringIdx var, NodeIdx start, NodeIdx end, // bodyCount, body0, body1, ...] const AstNode &n = nodes_.get(idx); ExtraIdx extra = n.lhs; @@ -492,7 +493,7 @@ Result Analyzer::analyzeFor(NodeIdx idx, NameMap state) { } Result Analyzer::analyzeMatch(NodeIdx idx, NameMap state) { - // MatchNode: d.lhs = NodeIdx (scrutinee), d.rhs = ExtraIdx → + // MatchNode: d.lhs = NodeIdx (scrutinee), d.rhs = ExtraIdx -> // [armCount, // arm0_patIdx, arm0_bodyCount, arm0_body..., // arm1_patIdx, arm1_bodyCount, arm1_body..., ...] @@ -558,7 +559,7 @@ Result Analyzer::analyzeReturn(NodeIdx idx, NameMap state) { } Result Analyzer::analyzeCall(NodeIdx idx, NameMap state) { - // Call: d.lhs = StringIdx (callee), d.rhs = ExtraIdx → [argCount, args...] + // Call: d.lhs = StringIdx (callee), d.rhs = ExtraIdx -> [argCount, args...] const AstNode &n = nodes_.get(idx); const FunctionAST *callee = nullptr; if ((n.flags & 1) == 0) { @@ -748,7 +749,7 @@ Analyzer::BorrowPath Analyzer::extractPath(NodeIdx argIdx) const { // Two paths overlap when one is a prefix of the other (or they are // equal). Per MVS.md §4.1: -// - Same base required; different bases → disjoint. +// - Same base required; different bases -> disjoint. // - Step-by-step compare on common prefix: // Field vs Field: same name? continue : disjoint // Index vs Index: both const & equal? continue : differ-const? disjoint @@ -784,7 +785,7 @@ bool Analyzer::pathsOverlap(const BorrowPath &a, const BorrowPath &b) { } Result Analyzer::analyzeStructLit(NodeIdx idx, NameMap state) { - // StructLit: d.rhs = ExtraIdx → + // StructLit: d.rhs = ExtraIdx -> // [fieldCount, fieldName0, fieldExpr0, fieldName1, fieldExpr1, ...] const AstNode &n = nodes_.get(idx); ExtraIdx extra = n.rhs; diff --git a/src/init_analysis.h b/src/init_analysis.h index 7695a10..5100494 100644 --- a/src/init_analysis.h +++ b/src/init_analysis.h @@ -55,7 +55,7 @@ struct Diagnostic { std::string varName; }; -// Map of function name → FunctionAST* used by the analyzer to look up +// Map of function name -> FunctionAST* used by the analyzer to look up // callee parameter modes for callsite propagation. The map is // borrowed; the analyzer never owns the FunctionAST pointers. May be // null, in which case mode propagation is conservatively skipped at diff --git a/src/jam_llvm.cpp b/src/jam_llvm.cpp index dca9b0e..bca3927 100644 --- a/src/jam_llvm.cpp +++ b/src/jam_llvm.cpp @@ -1190,7 +1190,7 @@ bool JamLLVMEmitObjectFile(JamModuleRef mod, JamTargetMachineRef tm, mpm.run(*M, mam); // LTO modes emit bitcode; the linker will run the rest of the pipeline. - // Otherwise lower IR → MIR → object via the legacy codegen-PM (LLVM has + // Otherwise lower IR -> MIR -> object via the legacy codegen-PM (LLVM has // not migrated codegen to the new PM yet). if (lto != JAM_LTO_OFF) { llvm::WriteBitcodeToFile(*M, dest); diff --git a/src/jam_llvm.h b/src/jam_llvm.h index 123f4e0..4c9a551 100644 --- a/src/jam_llvm.h +++ b/src/jam_llvm.h @@ -56,8 +56,8 @@ typedef enum { JAM_ICMP_SLE = 41, // signed less or equal } JamIntPredicate; -// Float comparison predicates. `O` = ordered (NaN inputs → false); -// `U` = unordered (NaN inputs → true). Jam emits the ordered +// Float comparison predicates. `O` = ordered (NaN inputs -> false); +// `U` = unordered (NaN inputs -> true). Jam emits the ordered // variants by default so NaN never satisfies a comparison. typedef enum { JAM_FCMP_FALSE = 50, // always false diff --git a/src/jir.h b/src/jir.h index 28e54aa..3518779 100644 --- a/src/jir.h +++ b/src/jir.h @@ -24,7 +24,7 @@ class FunctionAST; // needed. // // Pipeline (target): -// Source → Tokens → AST → AstGen → JIR → Codegen → LLVM IR +// Source -> Tokens -> AST -> AstGen -> JIR -> Codegen -> LLVM IR // // Each `JirInst` carries a typed result (or `kNoType` for control-flow // instructions) plus a source line for diagnostics. Variable-width @@ -140,8 +140,8 @@ enum class JirTag : uint8_t { // Control flow // Br: `a` = JirBlockRef target. No result. - // CondBr: `a` = cond ref; `b` = ExtraIdx → [thenBlock, elseBlock]. - // Switch: `a` = scrut ref; `b` = ExtraIdx → + // CondBr: `a` = cond ref; `b` = ExtraIdx -> [thenBlock, elseBlock]. + // Switch: `a` = scrut ref; `b` = ExtraIdx -> // [defaultBlock, caseCount, // case0_lo, case0_hi, case0_signed, case0_block, // case1_..., ...] @@ -160,11 +160,11 @@ enum class JirTag : uint8_t { // Function call // Call: `a` = StringIdx (callee qualified name); - // `b` = ExtraIdx → [argCount, arg0, arg1, ...] + // `b` = ExtraIdx -> [argCount, arg0, arg1, ...] // `ty` = return type (kNoType for void). Call, // CallIndirect: `a` = JirRef of a fn-typed value (the function - // pointer); `b` = ExtraIdx → [argCount, arg0, ...]; + // pointer); `b` = ExtraIdx -> [argCount, arg0, ...]; // `ty` = return type. The signature (return + params) // is read from the JIR-level Fn TypeIdx on `a` and // used by codegen to build the LLVM function type @@ -186,11 +186,12 @@ enum class JirTag : uint8_t { Param, // Aggregates - // StructLit: `b` = ExtraIdx → [fieldCount, field0_val, field1_val, ...]; + // StructLit: `b` = ExtraIdx -> [fieldCount, field0_val, field1_val, + // ...]; // `ty` = struct type. // FieldAccess: `a` = base ref, `b` = field index; `ty` = field type. // ExtractValue: `a` = aggregate ref, `b` = field index; `ty` = field type. - // ArrayLit: `b` = ExtraIdx → [count, elem0, elem1, ...]; + // ArrayLit: `b` = ExtraIdx -> [count, elem0, elem1, ...]; // `ty` = array type. // Index: `a` = base ref, `b` = index ref; `ty` = element type. StructLit, diff --git a/src/jir_codegen.cpp b/src/jir_codegen.cpp index 17ddbdc..8987a0e 100644 --- a/src/jir_codegen.cpp +++ b/src/jir_codegen.cpp @@ -19,9 +19,9 @@ namespace { -// Per-function lowering state: maps JirRef → LLVM Value so dataflow +// Per-function lowering state: maps JirRef -> LLVM Value so dataflow // references resolve as instructions are emitted in block order. -// Also maps JirBlockRef → LLVM BasicBlock for terminators. +// Also maps JirBlockRef -> LLVM BasicBlock for terminators. struct JirCodegenCtx { const JirFunction &jfn; JamCodegenContext &ctx; @@ -631,7 +631,7 @@ static JamValueRef emitInstImpl(JirCodegenCtx &lctx, JirRef r) { } if (calleeUsesSret) { // The LLVM call itself returns void; the value lives in - // the sret slot. Load it so the JirRef → LLVM value map + // the sret slot. Load it so the JirRef -> LLVM value map // holds the materialized return value. JamLLVMBuildCall(lctx.ctx.getBuilder(), f, args.data(), static_cast(args.size()), ""); @@ -714,7 +714,7 @@ static JamValueRef emitInstImpl(JirCodegenCtx &lctx, JirRef r) { // Specialization, mirroring rustc_codegen_ssa/mir/block.rs:: // codegen_switchint_terminator: collapse Switch back to a // cond_br when the shape is trivial. - // * 1 case + default → ICmpEq + CondBr (always valid: the + // * 1 case + default -> ICmpEq + CondBr (always valid: the // default is the cond_br's `else` whether it's a live // fall-through block or an unreachable one). // Rust also collapses 2-case bool shapes — that requires diff --git a/src/jir_codegen.h b/src/jir_codegen.h index 9c41b6e..a0e46c3 100644 --- a/src/jir_codegen.h +++ b/src/jir_codegen.h @@ -15,7 +15,7 @@ class JamCodegenContext; // JIR codegen — walks a fully-typed JirFunction and emits LLVM IR // instructions. By design this stage is mechanical: there is no type // inference, no peer resolution, no divergence analysis. Each JirInst -// maps to a small handful of LLVM IR instructions and the JirRef → +// maps to a small handful of LLVM IR instructions and the JirRef -> // LLVM Value mapping carries the dataflow. // // Two-step API: prototype emission runs first so forward references diff --git a/src/lexer.cpp b/src/lexer.cpp index b95a510..5a5706a 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -176,11 +176,11 @@ void Lexer::identifier() { // number_literal.cpp. // // Greedy character set: -// . → entering a float (or `..=` separator — see below) -// _ → digit separator -// 0-9, a-z, A-Z → digits or future suffix letters -// e, E, p, P → exponent markers -// + / - → exponent sign immediately after p/P/e/E +// . -> entering a float (or `..=` separator — see below) +// _ -> digit separator +// 0-9, a-z, A-Z -> digits or future suffix letters +// e, E, p, P -> exponent markers +// + / - -> exponent sign immediately after p/P/e/E // // Escape hatch: // `..` (followed by a non-digit) ends the number — required so that @@ -201,7 +201,7 @@ void Lexer::negativeNumber() { } void Lexer::scanNumberBody() { - // State machine: int → int_period → float → float_exp. We track + // State machine: int -> int_period -> float -> float_exp. We track // only enough state to know whether `+`/`-` is valid (it is only // after a p/P/e/E exponent letter). enum NumState { S_INT, S_INT_PERIOD, S_FLOAT, S_FLOAT_EXP }; diff --git a/src/mangling.h b/src/mangling.h index 6e6bb3b..2b833b0 100644 --- a/src/mangling.h +++ b/src/mangling.h @@ -19,19 +19,19 @@ // symbol names, so no further escaping is needed. // // The rules: -// - `tfn t()` → `__test_t` (the harness in main.cpp calls these by +// - `tfn t()` -> `__test_t` (the harness in main.cpp calls these by // prefixed name). // - Cloned instantiated methods carry their qualified name already // (`Vec__i32.push`) — we keep it. -// - Free-function `cfn drop(self: mut T)` → `T.drop`. The receiver +// - Free-function `cfn drop(self: mut T)` -> `T.drop`. The receiver // type qualifies it so two top-level drops for different types // don't collide on the bare name `drop`. Matches what an // equivalent in-struct `cfn drop` would mangle to. // - Otherwise: `[modulePath.][parentStruct.]Name` -// free fn in entry module → `name` -// free fn in module `m` → `m.name` -// method on `T` in entry module → `T.name` -// method on `T` in module `m` → `m.T.name` +// free fn in entry module -> `name` +// free fn in module `m` -> `m.name` +// method on `T` in entry module -> `T.name` +// method on `T` in module `m` -> `m.T.name` // // Centralised so every site that picks a function's LLVM symbol // (`jirDeclarePrototype`, `astgen::emitCall`, generic instantiation in diff --git a/src/parser.cpp b/src/parser.cpp index 02dfd2b..92c5072 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -163,7 +163,7 @@ static std::string chainRootName(const NodeStore &ns, const StringPool &pool, } // Count the number of dots (== MemberAccess hops) in a qualified-name -// chain. `foo` → 0, `foo.bar` → 1, `foo.bar.baz` → 2, etc. +// chain. `foo` -> 0, `foo.bar` -> 1, `foo.bar.baz` -> 2, etc. static int chainDotCount(const NodeStore &ns, NodeIdx chainRoot) { const AstNode &n = ns.get(chainRoot); if (n.tag != AstTag::MemberAccess) return 0; @@ -181,7 +181,7 @@ NodeIdx Parser::parsePrimary() { // * Type-arg form (`@sizeOf(T)`, `@alignOf(T)`): single TypeIdx // stored in rhs. flags=0. // * Expr-arg multi-form (`@emit*(...)` intrinsics callable from - // cfn bodies): rhs is an ExtraIdx → [argCount, arg0, ...]. + // cfn bodies): rhs is an ExtraIdx -> [argCount, arg0, ...]. // flags bit 0 = 1. // The intrinsic name decides which encoding the parser uses. if (match(TOK_AT)) { diff --git a/tests/cpp/test_abi.cpp b/tests/cpp/test_abi.cpp index 3cbbec1..a774ba3 100644 --- a/tests/cpp/test_abi.cpp +++ b/tests/cpp/test_abi.cpp @@ -7,11 +7,11 @@ // the resulting ParamABI / ReturnABI shape. // // Reference behavior (from docs/ABI.md §3): -// mut → ByPointer (any size) -// let / move, scalar T → ByValue +// mut -> ByPointer (any size) +// let / move, scalar T -> ByValue // let / move, aggregate -// size <= 16 bytes → ByValue -// size > 16 bytes → ByPointer +// size <= 16 bytes -> ByValue +// size > 16 bytes -> ByPointer #include "../../src/abi.h" #include "../../src/ast.h" diff --git a/tests/cpp/test_analyzer.cpp b/tests/cpp/test_analyzer.cpp index ebfb755..09935dd 100644 --- a/tests/cpp/test_analyzer.cpp +++ b/tests/cpp/test_analyzer.cpp @@ -1,5 +1,5 @@ // Unit tests for src/analyzer.h — the demand-driven Decl chokepoint. -// Covers the state-machine transitions (Unreferenced → InProgress → +// Covers the state-machine transitions (Unreferenced -> InProgress -> // Complete), the cycle detector (Decl.analysis re-entry produces a // "dependency loop" diagnostic with the chain), and the struct-status // chokepoint (Struct.status FieldTypesWIP re-entry produces a @@ -95,7 +95,7 @@ void testInProgressTriggersCycleError() { void testCycleChainListsAnalysisStackInOrder() { // When the analyzer hits a cycle, the message should list the - // chain in source order: A → B → A. We populate the analysis + // chain in source order: A -> B -> A. We populate the analysis // stack ourselves and ask the analyzer to "re-enter" A from the // middle of B's analysis. JamCodegenContext ctx("test_module"); @@ -207,7 +207,7 @@ void testEnsureDeclAnalyzedFunctionPopulatesSignatureCache() { decls.get(fn2).fnAst = &realFn; az.ensureDeclAnalyzed(fn2); ASSERT_TRUE(decls.get(fn2).signature.computed); - // Empty Args → empty params vector; kNoType return → Direct ABI + // Empty Args -> empty params vector; kNoType return -> Direct ABI // classification's default (matches what classifyReturn returns // for the void-like case). ASSERT_EQ(static_cast(0), @@ -276,7 +276,7 @@ void testAnalysisStackEmptyAfterTopLevelCall() { int main() { TestFramework framework; - framework.addTest("Analyzer - Unreferenced → InProgress → Complete", + framework.addTest("Analyzer - Unreferenced -> InProgress -> Complete", testUnreferencedTransitionsThroughInProgressToComplete); framework.addTest("Analyzer - Complete returns cached value", testCompleteDeclReturnsCachedValueNoReanalysis); @@ -287,7 +287,7 @@ int main() { framework.addTest("Analyzer - cycle chain lists stack in source order", testCycleChainListsAnalysisStackInOrder); framework.addTest( - "Analyzer - resolveTypeFieldsStruct: None → HaveFieldTypes", + "Analyzer - resolveTypeFieldsStruct: None -> HaveFieldTypes", testResolveTypeFieldsStructNoneToHaveFieldTypes); framework.addTest( "Analyzer - resolveTypeFieldsStruct: WIP is self-cycle error", diff --git a/tests/cpp/test_codegen_errors.cpp b/tests/cpp/test_codegen_errors.cpp index 0f02d5e..13126d6 100644 --- a/tests/cpp/test_codegen_errors.cpp +++ b/tests/cpp/test_codegen_errors.cpp @@ -38,7 +38,7 @@ CompileResult compileSource(const std::string &name, out << source; } - // Redirect stderr→stdout so popen captures both. jam.out usually + // Redirect stderr->stdout so popen captures both. jam.out usually // only writes to stderr on error, but this is robust either way. std::string cmd = "./jam.out " + path + " 2>&1"; @@ -249,7 +249,7 @@ fn main() i32 { return 0; } } // Float-typed destinations need a float literal (`3.0`) or an - // explicit `as` cast. Implicit int→float coercion is rejected so + // explicit `as` cast. Implicit int->float coercion is rejected so // the source spells out every bit-pattern change. static void testIntToFloatRejected() { auto r = compileSource("must_fail_int_to_float", R"( @@ -558,7 +558,7 @@ fn main() { ASSERT_TRUE(stderrContains(r, "unsupported `as` cast")); } - // Mirror of the above for the other direction: only u64 → ptr + // Mirror of the above for the other direction: only u64 -> ptr // is accepted (a u32 can't carry a full target pointer). static void testNarrowIntAsPtrRejected() { auto r = compileSource("narrow_int_as_ptr", R"( diff --git a/tests/cpp/test_comptime.cpp b/tests/cpp/test_comptime.cpp index db653d8..a514fef 100644 --- a/tests/cpp/test_comptime.cpp +++ b/tests/cpp/test_comptime.cpp @@ -610,7 +610,7 @@ void testExecWhileIterationCapTrips() { e.execStmt(whileNode, scope, iter, /*iterCap=*/100, ret, diags, loc); ASSERT_TRUE(r == jam::ExecResult::IterationCap); ASSERT_TRUE(diags.hasErrors()); - // 100 iterations ran (i went 0→1→…→100); the 101st loop attempt + // 100 iterations ran (i went 0->1->…->100); the 101st loop attempt // trips the cap before the body runs. ASSERT_EQ(static_cast(100), scope.lookup("i")->asU64()); } diff --git a/tests/cpp/test_decl_table.cpp b/tests/cpp/test_decl_table.cpp index 9af195f..f6bd704 100644 --- a/tests/cpp/test_decl_table.cpp +++ b/tests/cpp/test_decl_table.cpp @@ -64,7 +64,7 @@ void testDependencyEdgesAreSymmetric() { jam::DeclIndex b = t.create(jam::DeclKind::Function, "b"); jam::DeclIndex c = t.create(jam::DeclKind::Function, "c"); - // a → b, a → c, b → c + // a -> b, a -> c, b -> c t.declareDependency(a, b); t.declareDependency(a, c); t.declareDependency(b, c); diff --git a/tests/cpp/test_diagnostics.cpp b/tests/cpp/test_diagnostics.cpp index 72caa91..92a5425 100644 --- a/tests/cpp/test_diagnostics.cpp +++ b/tests/cpp/test_diagnostics.cpp @@ -365,7 +365,7 @@ void testSelfReferentialUnionIsCaught() { } void testStructViaUnionCycleIsCaught() { - // Mixed struct→union→struct chain. The cycle key here is on the + // Mixed struct->union->struct chain. The cycle key here is on the // inner struct's FieldTypesWIP — the union's body fill triggers // ensureStructBody, which re-enters S while it's still WIP. auto r = compileSource("diag_struct_via_union",