Commit Graph

1 Commits

Author SHA1 Message Date
Brummel 1fd4763fad Iter 12b: codegen monomorphisation for polymorphic defs
Polymorphic defs are now actually emitted: each unique instantiation
gets its own specialised LLVM fn, mangled @ail_<m>_<def>__<descriptor>.
The typechecker's Forall instantiations from Iter 12a now have a real
backend.

Strategy:
- Pass 1 of lower_workspace splits fn-typed defs into mono and poly:
  module_user_fns keeps LLVM-typed FnSig only for monomorphic fns;
  module_polymorphic_fns holds the full FnDef for poly defs;
  module_def_ail_types is a unified AILang-type lookup for both.
- Direct calls to poly defs (current-module or qualified cross-module)
  flow through lower_polymorphic_call: it derives the type
  substitution from the actual arg AILang types, mangles the symbol,
  and queues (def, subst) for specialisation.
- emit_module drains the queue after the regular defs. Each entry
  produces a specialised FnDef with rigid vars substituted in both
  the type and the body (incl. Lam param/ret annotations), then
  emits via the existing emit_fn pipeline. The synthetic name embeds
  the descriptor; standard mangling produces the right symbol.

Codegen-side type tracking:
- locals tuple grew from (name, ssa, llvm_type) to 4-tuple with
  ail_type. Updated all 6 push sites: fn entry, Let, match-ctor field,
  match-open-arm, lambda capture (cap_meta also extended), lambda
  param.
- CtorRef gained ail_fields parallel to fields, used by match-arm
  bindings to inherit AILang types.
- New synth_arg_type / synth_with_extras: a small recursive walker
  that derives the AILang type of an expression in the current scope.
  Used at poly call sites for arg-type inference. The `extras`
  parameter shadows locals during Let recursion without &mut self.

Helpers added:
- derive_substitution(vars, params, arg_tys): unifies the param
  shapes against the actual args, binding rigid vars; reports
  unbound vars as an internal error.
- apply_subst_to_type / apply_subst_to_term: substitute rigid vars
  throughout a Type or Term (the Term variant only matters for Lam,
  the only Term arm carrying types).
- descriptor_for_subst / type_descriptor: stable identifier-safe
  name suffix. `Int → I`, `Bool → B`, `Unit → U`, `Str → S`, ADT
  `Foo → FFoo`, `Fn → Fn_<...>__r_<ret>`. So `id__I` for id at Int,
  `apply__I_I` for apply at (Int, Int).
- builtin_ail_type / builtin_effect_op_ret: AILang types for the
  builtin operators and effect ops, used by the codegen-side type
  tracker.

Examples + tests:
- examples/poly_id.ail.json: id used at Int (42) and Bool (true).
  Output: 42 / true. Two specialised fns + adapters + static
  closures get emitted (visible in the IR).
- examples/poly_apply.ail.json: apply(succ, 41) == 42. Exercises
  the harder case where one of the polymorphic params is itself
  a function value — the closure-pair ABI survives substitution.
- crates/ail/tests/e2e.rs: polymorphic_id_at_int_and_bool and
  polymorphic_apply_with_fn_param.

Tests: 58/58 (was 56/56). Hash invariant holds: sum.ail.json keeps
db33f57cb329935e / d9a916a0ed10a3d3.

Limitations (known, deferred):
- Polymorphic fns can only be DIRECTLY called. Passing a poly fn as
  a value (let f = id in f(42)) fails — resolve_top_level_fn looks
  in module_user_fns which doesn't include poly defs. Adding this
  needs per-instantiation closure-pair globals.
- No higher-rank polymorphism: a polymorphic arg passed to another
  polymorphic call (apply(id, 42)) trips the simple unify_for_subst
  which doesn't recurse into Forall. Acceptable for the MVP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:10:51 +02:00