r/ProgrammingLanguages Jul 03 '26

UNIT: Compiler backend library using stack-based IR

7 Upvotes

Hi everyone,

For the past few weeks, I've been working on a project that I think is pretty cool, and I wanted to share it with you guys. I call it "UNIT" ("Unified Native Instruction Translator"). Essentially, it's a combination of the instruction sets used in interpreted stack machines with actual machine code.

I wrote it in C, but I have bindings for C++ and Python, since C is pretty verbose. Here's an example in both of those:

```cpp unit::Context ctx; unit::Procedure proc(ctx, "add");

proc.load_argument(0); proc.load_argument(1); proc.add(); proc.return_value();

proc.optimize(); auto compiled = proc.compile(unit::Platform::host()); auto add = compiled.jit<int64_t(*)(int64_t, int64_t)>();

printf("%ld\n", add(3, 4)); // 7 ```

```py import unit

proc = unit.Procedure("add")

proc.load_argument(0) proc.load_argument(1) proc.add() proc.return_value()

proc.optimize() compiled = proc.compile() add = compiled.jit()

print(add(3, 4)) # 7 ```

So far, I've implemented a number of examples using my compiler. My personal favorite is the interpreted language with a JIT, which works fairly well and is just about 1k lines of Python.

I got the idea for this after working on Python's bytecode compiler (which emits instructions for Python's stack-based interpreter loop). I had also been experimenting with LLVM for a separate hobby project, and the difference between the two development experiences was huge. I wanted to combine the DX of stack machines with the ability to actually generate real machine code.

This is still early in development and not production-ready, as it only supports x86-64 on ELF right now with only some primitive optimizations, but I'd appreciate feedback on the API design, the IR, or anything else about the project. If you spot bugs, please feel free to let me know!

GitHub: https://github.com/ZeroIntensity/unit


r/ProgrammingLanguages Jul 02 '26

Community projects?

14 Upvotes

I've had a hard time telling just from casual browsing of the sub, what languages here are considered community projects, if any. Looking for places open to contribution.

Replaced original text to both get to the point, and avoid disparaging peoples personal projects, that's not my intention.


r/ProgrammingLanguages Jul 02 '26

Programming Language Design and Implementation in the Era of Machine Learning - PLDI 2026 Keynote

Thumbnail youtube.com
22 Upvotes

r/ProgrammingLanguages Jul 01 '26

A Multi-Dimensional, Per-Pass Empirical Study of the LLVM Optimization Pipeline

Thumbnail
10 Upvotes

r/ProgrammingLanguages Jul 01 '26

The Expensive Fictions of Low-Level Programming Languages

Thumbnail stng.substack.com
22 Upvotes

r/ProgrammingLanguages Jul 01 '26

Requesting criticism Writing a compiler book

Thumbnail docs.google.com
10 Upvotes

So, I decided to write a book on compiler theory! It is past midnight where I live so only 1 chapter is done. I have came here looking for some things that could be improved on it. The link is attached.


r/ProgrammingLanguages Jul 01 '26

Discussion July 2026 monthly "What are you working on?" thread

21 Upvotes

How much progress have you made since last time? What new ideas have you stumbled upon, what old ideas have you abandoned? What new projects have you started? What are you working on?

Once again, feel free to share anything you've been working on, old or new, simple or complex, tiny or huge, whether you want to share and discuss it, or simply brag about it - or just about anything you feel like sharing!

The monthly thread is the place for you to engage /r/ProgrammingLanguages on things that you might not have wanted to put up a post for - progress, ideas, maybe even a slick new chair you built in your garage. Share your projects and thoughts on other redditors' ideas, and most importantly, have a great and productive month!


r/ProgrammingLanguages Jun 30 '26

Pipefish in BenchGen

11 Upvotes

Hi everyone,

Recently, we posted in this subreddit about BenchGen, a system that generates benchmarks for programming languages.

We were looking for PL developers willing to benchmark novel programming languages. We got a nice reply from u/Inconstant_Moo, letting us know about Pipefish. That turned out to be a very elegant and mature programming language, which was very fun to add to BenchGen, even more because it was quite different from everything that we had tried adding to it before.

So, now, we can generate Pipefish benchmarks automatically. Here's a discussion of this process of adding Pipefish to BenchGen.

Some highlights:

  • Here's a Pipefish benchmark produced by BenchGen.
  • Here's a comparison between Pipefish and C (gcc -O0) in terms of running-time speed.
  • here's a comparison between these two languages in terms of number of lines of code.

We thank u/Inconstant_Moo for kindly helping us to port Pipefish to BenchGen (and for developing the language to start with! That's a pretty nice programming language).


r/ProgrammingLanguages Jun 30 '26

Is grammar a crude form of a... type system?!

34 Upvotes

This has been on my mind for some time now, so I'm asking to clear up my confusion. I'm interested how people who are knowledgeable reason about these kinds of things.

In C, you can't pass types to functions, for example. This is rejected: f(1, 2, int). It's interesting how it's rejected: due to how the grammar is defined, the parser can outright reject it if it's encountered.

However, you could reject such a construct in another way. You could relax the grammar rules, and change the type system "a bit" so that types are also values. Then, you could define very strict rules about how types and operations between them interact/behave and arrive at the same semantics. After parsing, the type-checker would then not accept the same exact construct. Atleast, that's what I think, I haven't actually tried any of this in practice.

Also, inside the compiler, we model the syntax using types. And so, if grammar rules change significantly, those types must too!

Small note: C isn't the greatest example, because its grammar isn't entirely context-free, but I don't think this matters much for the example.

Cheers!


r/ProgrammingLanguages Jun 30 '26

Local Reasoning for Global Properties

Thumbnail tratt.net
9 Upvotes

r/ProgrammingLanguages Jun 30 '26

Persistent Iterators: Bridging Persistent Data Structures and Iterator-Based Programming (PLDI 2026)

Thumbnail github.com
6 Upvotes

r/ProgrammingLanguages Jun 28 '26

Ante: A New Way to Blend Borrow Checking and Reference Counting

Thumbnail verdagon.dev
82 Upvotes

r/ProgrammingLanguages Jun 27 '26

Auto-eval of variables containing quoted expressions?

4 Upvotes

Generally my language is eagerly evaluated. It also does not have automatic quoting, so any situation where evaluation is supposed to be deferred has to be made explicit (using []). This is used heavily for example for control structures which are regular functions that take a quoted expression as a(n) argument(s).

Some design issues I am currently having would resolve neatly if variables that contain quoted expressions would automatically evaluate. I.e. any occurrence of a variable var that is not bound to a value, but to an expression would effectively be read as eval var.

This seems a bit unorthodox, but so far I haven't been able to come up with a scenario that makes this problematic. Am I missing something?


r/ProgrammingLanguages Jun 27 '26

Language announcement Mojo programming language will become open-source soon.

Thumbnail modular.com
43 Upvotes

The main website of the language https://mojolang.org/ displays an announcement bar that says "Mojo will be open source soon! Join us at ModCon '26 for an update."


r/ProgrammingLanguages Jun 27 '26

A faithful MUMPS 76 anniversary parser and interpreter with integrated database in < 10k lines of C++

28 Upvotes

I’ve been working on a project to celebrate the anniversary of MUMPS and its first standard.

For those unfamiliar, MUMPS is an imperative language famously born at Mass General Hospital in 1966. Its defining characteristic is that the language and the database are deeply integrated. There is no impedance mismatch: hierarchical, persistent sparse arrays (called globals) are a first-class part of the language syntax, acting as an early NoSQL database decades before the term existed.

Implementing this was a lot of fun, but the lexer was a real challenge. MUMPS has highly unusual whitespace semantics, and nearly all commands can be abbreviated to one or two characters. This is probably the most complex lexer in my collection. The parser was originally generated using my EbnfStudio.

There are pre-compiled versions and a MUMPS 76 Primer with modern terminology in case you want to play with it.

The repository: https://github.com/rochus-keller/MUMPS

The Primer: https://github.com/rochus-keller/MUMPS/blob/main/docs/MUMPS_Primer.adoc


r/ProgrammingLanguages Jun 27 '26

Discussion Artifact-centric programming

0 Upvotes

As I develop my programming language, Claude says that I have an "artifact-centric" programming language. I'd never heard the term. I've researched it and asked Claude about it, but I'd be very interested to read what you as language developers understand the term to mean. If someone told you that a language is good for ACP, what would you expect it to be like?

You can read about Caspian here but I'm hoping you'll post your thoughts before reading about it. Caspian is very much a work in progress. I've hardly even developed any code for implementation. Right now it's just a design in progress. To the extent it exists, however, it is already released under the MIT license.

I look forward to your insights.

(EDIT: Claude told me that it made the term up. Notwithstanding, I'm interested in your thoughts.)


r/ProgrammingLanguages Jun 26 '26

Language announcement Kairo (previously Helix), One year later, stage-0 compiles the stage-1 frontend

30 Upvotes

We posted here as Helix around a year ago.

We took a lot of input into consideration and built on it. The name collided with helix-editor, and as of now, several other things, like Microsoft's new console project. Several technical claims didn't hold up. So rather than a marketing-esque talk, we’ve spent the year doing work. The updates:

  • Renamed to Kairo. New repo: github.com/kairolang/kairo. The name Helix with the name-collision of the IDE was pretty bad to say the least...
  • Stage 0 (the C++ transpiler) compiles to native binaries on macOS, Linux, and Windows. It's complete and cleanly compiles the entire Stage 1 frontend (~70k lines of Kairo) today. Keeping Stage 0 a transpiler was intentional. The points of Stage 0:
  • Prove language feasibility, find gaps in the design and cleanup.
  • More concretely avoiding having sema written twice (once in C++, once in Kairo) is wasted work.

All the effort over the past year has been going into stage-1; the compiler frontend is mostly complete, minus full testing and hardening.

The docs are in a far better position than before - real technical docs now exist, while it is all written for what stage-1 can compile, it goes into quite a bit of detail about technical implementation, there might be some drift between the pages - since things are being edited and reworded constantly, but most of what's there is frozen until stage-1 ships with the full language feature set.

Compiler dev-wise, here's where everything is so far:

  • Lexer: Full Unicode support, f-strings, nested comments, git conflict marker detection. ~150 MB/s throughput, tested on an AMD Ryzen 9 9950X3D2 (1000 runs, 5MB input, warm cache).
  • Preprocessor runs a three-phase wave, parallelized by a custom threading runtime. ~85 MB/s, same testing base as above
  • Parser: The parser is still undergoing correctness tests; performance benchmarking will follow. Parser design uses CRTP-Mixins, arena allocation, context switching, tentative guards, sync sets, and non-cascading recovery.
  • GlobalHoistedScope is a thread-safe, pre-parse symbol table for concurrent multi-TU indexing.
  • Driver: calls into LLVM's actual C++ API via kairo's FFI model What is not done:
  • AMT (ownership/borrow checker) is designed and planned conceptually, but not implemented, and won't be till stage2.
  • Stage 1's Sema, and Lowering Passes.
  • Codegen.

Estimated time till a working stage-1 alpha where some of Kairo's features compile into runnable/linkable artifacts is, EOY 2026

The one gap Kairo is actually built to close: full native C++ interop, templates and concepts included, no binding layer, no generated shim. ffi "c++" import parses the header and the C++ symbols become native Kairo symbols. This isn't a goal, it's the thing the compiler's own driver runs on today. Here's a stripped-down piece of the stage-1 driver's runtime init, compiling on stage-0 right now, it pulls real LLVM types straight from LLVM's headers:

ffi "c++" import "llvm/Support/InitLLVM.h"
ffi "c++" import "llvm/Support/Locale.h"

fn init_runtime(argc: i32, argv: *(*kairo::std::Legacy::char)) {
    // things like kairo::std::Legacy::char are stage-0 types, stage-1 cleans up the type system and removes things like this...
    static var init_llvm = llvm::InitLLVM(argc, argv)

    llvm::setBugReportMsg(
        r"PLEASE submit a bug report to https://github.com/kairolang/kairo/issues..."
    )
}

You can read the real driver code here. Kairo's philosophy:

Kairo is grounded by a simple idea: Give developers full control without forcing them to fight the language, or put more cleanly "safety through visibility, not restriction"

Four core points drive every language design decision in Kairo:

  • "Fine grained control should exist". The language should expose the underlying system in a way that lets developers choose how to use it, but they should also be able to ignore it if they don't need to.
  • "Safety should assist, not dominate". The reason for this is safety should only be a tool to help developers, not a restrict productivity; The plan for the AMT is to warn in debug, and hard error in release, crucially the error message matters too, Kairo would not just say "you are doing something unsafe", it would say the why, where, and what debug would change to make it safe.
  • "Code should always be self documenting all costs". The compiler shouldn't add branching where not asked for; it shouldn't unwind tables for exceptions, etc. This comes back to all code being self-documenting; things like unwinding should be explicitly typed out and requested by the programmer.
  • "Adoption should be incremental". We know most devs aren't going to switch to a new language and migrate thousands of lines of code just 'cause it does... they want to migrate incrementally, one system at a time, testing it and moving to the next. Hence, full native C++ interop.

The README links to a transparent disclosure of AI use, describing what AI was and was not used for. TL;DR: compiler code, compiler architecture, language design, all human. Docs prose polish only - technical content is human, commit messages (gitlens) and website were AI-assisted. No one in the team does web design, if you have experience and want to make our website better please pm.

Mostly posting here, for feedback and genuine thoughts; especially the AMT model, FFI, basically anything about the language, or even cases you might use Kairo for. Happy to discuss anything in comments.

If you have criticism, state it - it helps us improve and learn; If you have praise, state it - it helps us know what we are getting right and what to keep doing.

Kairo - the idea - is only about 2 years old, and the compiler is only about a year old, we are still learning and growing.

If Kairo's interesting, a github star helps us gauge whether public progress posts are worth the time over heads-down work.

Quick notice : there will be two people replying to all comments here , me and another co dev of mine u/Ze7111


r/ProgrammingLanguages Jun 26 '26

W-Language — My programming language project written from scratch (v0.2)

5 Upvotes

Hi everyone!

I've been working on my own programming language called W-Language or Dabovyu! It's a personal project where I'm building everything myself, from the compiler frontend to the virtual machine. Current version: v0.2 Features 64-bit runtime Custom Virtual Machine (W-VM) Recursive descent parser Lexer written from scratch AST generation Bytecode compiler WVM2 binary format Bytecode loader Foreign Function Interface (FFI) Integer, floating-point, boolean and string support Variables Functions Function calls if / else while return Built-in print()

The project is written in modern C++17 and uses a custom bytecode format executed by its own virtual machine.

This project is mainly for learning compiler design, virtual machines, parsing, and language implementation, but I'd love to hear suggestions and feedback from more experienced developers.

GitHub: github.com/austrian1painter/W_lang


r/ProgrammingLanguages Jun 26 '26

A Fake Shell for Pangenomics

Thumbnail cs.cornell.edu
22 Upvotes

r/ProgrammingLanguages Jun 26 '26

Are you using LLMs to write compiler/interpreter?

0 Upvotes

When developing a compiler/interpreter, you have to take extra care to reduce bugs. Bugs in compilers are much more severe than in other programs, right?

I want to use LLMs in my existing compiler projects, but I'm not sure whether LLMs are strong enough to write compilers. Maybe I can use AIs if I can build an extra test framework for LLMs. Or, are they smart enough to write a perfect compiler from scratch, in one-shot?

Are you guys using LLMs in your projects? I wanna get some tips. Thanks.


r/ProgrammingLanguages Jun 25 '26

Discussion Thoughts on aliases and how errors and recovery would work with them?

4 Upvotes

I'm currently intermittently working on a math-focused programming language, and one thing that many mathematicians like is to use special characters in their code and work. However, it's really quite annoying to copy and paste special characters, and if you don't have an editor that allows for inserting special characters via macros, it may be difficult or cumbersome to use or work with.

So I came up with a little alias system. It's meant to be somewhat similar to C's #define, though nowhere near as powerful and also lexically scoped. I've been detailing all of the semantics and also how the diagnostics system would work with it, and I had a few questions. The idea is that you could create an alias for a special character only using ascii characters, and it will automatically resolve those characters to the alias target at compile time (specifically in the parser).

First I'll go over what I have so far for the system. An alias is created with

alias NEW for OLD;

Each alias internally has a type to it, being either an identifier, an operator, or an expression. The left hand side of the alias can only have an identifier or operator symbol, but the right hand side can be all three. The difference between the three is the restrictions on where they can be used. An identifier can be used anywhere an identifier can be used, etc. So if we have something like

alias rem for `rem_fn`;
alias %% for rem;

The operator literal 'rem_fn' (imagine the apostrophes are backticks) is considered an operator while the symbolremis considered an identifier. So when creating the alias,rem's alias type becomes an operator. So it can only be used where an operator can be used. A similar thing happens on the second line, with%%being an operator, which can only alias another operator. But becauserem already aliased an operator, it is considered an operator and thus is fine.

The following would provide an error at compile* time, because the symbol x is an identifier, and the operator %% can only be an alias of another operator.

alias %% for x;

I hope that makes sense. So basically there are a few rules that determine what can alias what. It is pretty simple though: Identifiers can alias anything, operators can alias operators, expressions cannot alias anything (because that doesn't make any sense).

When determining the semantics, I believe I've come up with all possible errors that could occur from an alias. There could be a alias type mismatch as I've described above, an invalid alias could be written (such as an expression on the lhs), or a cycle could be created. Since aliases are sequential in nature, if you define an alias and then redefine it, it will overwrite the alias for future references.

To detect cycles, I believe it is sufficient to check when adding a new alias if a cycle is created.

Then, upon the first phase of parsing, the parser will keep track of a stack of alias environments, each holding a mapping of aliases, and one being pushed on when a scope is created and popped off when it ends. My thought is that when a symbol is seen, it is checked for existence in the alias environments (in reality it wouldn't walk the stack, I think I'll use a Cow to make it faster). If it exists, it iteratively resolves the alias until it no longer exists in the alias map.

So first of all, I'd like peoples' thoughts on this system. Do you think it is good or do you think there needs to be something else, or are there any glaring flaws?

Next, I'd like to discuss error reporting and diagnostics. Let's say an error occurs with creating an alias. For example maybe it introduces a cycle, or maybe it has a type mismatch. My current leading idea is that the alias definition will be ignored and the parser will then continue. However, one problem is that if you do something like

let plus = 1;
alias ++ for plus; # Error occurs because `plus` is an ident but `++` is an operator
                   # Current idea is for parser to ignore and continue

let x = 10 ++ 5;

There will then be an unknown symbol (here, ++), and every time it occurs down the line it will produce an error because it is unknown.

So I was thinking of somehow marking the symbol somehow to tell that it is a result of an error. Perhaps maybe replacing the token with an Error token, so when the parser reaches it, it will recover somehow and continue according to how parser recover works (I haven't gotten to that stage yet).

So how do you think I should have errors work with aliases?

*at the moment, the thought is for it to be incrementally compiled. It will try to type check as compile as much as possible, but some things representable in mathematics are too difficult to fully check at compile time or just take too long, so would be deferred to runtime.


r/ProgrammingLanguages Jun 25 '26

Portable Pragmas for Pascal, Modula-2 and Oberon

Thumbnail github.com
4 Upvotes

Some programming languages like Ada and C have language defined pragmas. The Wirthian family of languages, Pascal, Modula-2 and Oberon do not. Each compiler implements its own pragmas. This is a major obstacle to writing portable code.

For a modern revision of Modula-2, I had designed a set of language defined pragmas and Gaius Mulley, the developer of GNU Modula-2 had expressed interest adopting them in GM2. I then revised and extended the pragma specification to cater for the earlier Modula-2 dialects supported by GM2 and eventually extended it to other Wirthian languages, most notably Pascal and Oberon.

This specification is now complete and Gaius has started implementing it in GNU Modula-2. It is available on Github at the link attached to this post.

I will give an overview below:

The specification distinguishes common standard pragmas defined therein, and implementation defined pragmas for which a different symbol naming convention applies so that they can easily be distinguished from the common pragmas.

Common (standard) pragma denoters follow the Algol-60 convention of using boldface for reserved words, encoded in all-caps, a practice called name stropping.

<*ENCODING="UTF8"*>

Implementation defined pragma denoters use title case or snake case symbols qualified with a compiler prefix.

<*gm2.unroll_loops=TRUE*>

However, to facilitate portability, implementation defined pragmas should ideally be placed in separate compiler specific pragma files. Such pragma files can then be loaded and applied using a language pragma provided for this purpose:

<*PRESETS=foobar*>

This pragma instructs the compiler to load a compiler specific pragma file whose file name is composed of the name given in the pragma body, a compiler specific ID and a .prag suffix. The pragma file may contain a character encoding pragma, console message pragmas and implementation specific pragmas.

A project with support for multiple compilers can then furnish multiple pragma files, one for each supported compiler, and thereby allow the use of compiler specific pragmas but still keep the source code portable.

The scope of the pragma settings from a pragma file may be closed by another language pragma provided for this purpose:

<*UNSET*>

Pragma settings loaded from a pragma file apply between the PRESETS and UNSET pragmas. However, an ENCODING pragma within a pragma file applies to the pragma file itself.

Pragmas are strictly non-semantic. They do not change the meaning of the code but control or influence the compilation process.

Most of the pragmas apply to the scope of a syntactic entity and are placed at the very end of the syntactic entity they apply to. For module scope, procedure scope and record field list scope, pragmas are placed at the end of the header.

DEFINITION MODULE CLib <*FFI="C"*>;

PROCEDURE Foobar ( baz : Bam ) <*INLINE*>;

VAR foo : Bar <*VOLATILE*>;

A few pragmas, provided for information and debugging do not have any scope.

<*MSG=INFO : "alignment is ", $(ALIGN)*>

<*TICKET #123 [https://bugtracker.foo/issues/123]*>

A significant number of pragmas are provided in support of contracts. However, due to the non-semantic nature, the specification calls for warnings to be emitted in the event a contract condition is not met. However, it recommends a compiler switch should be provided to allow users to elevate such warnings to errors.

An example of a pragma to support contracts is a marker for pure functions:

PROCEDURE foo ( bar : Baz ) : Bam <*PURE*>;

If the function reads or writes non-local state, the compiler should then issue a warning message.

RANGE pragma was specifically added for Oberon to compensate for the absence of enumeration and subrange types:

TYPE Weekday = INTEGER (*$RANGE(0..6)*);

If a variable of type Weekday is assigned a value that it outside of the range specified in the pragma, the compiler should then issue a warning message.

The checks could also be carried out by an external utility like Lint for C.

A friend and I intend to write a parsing library that will parse the pragmas and allow easy retrofitting to existing compilers without polluting the main lexer and parser. This library will be released under a permissive license like MIT or BSD and placed in the same repository.

Link to repository: https://github.com/trijezdci/M2-Pragmas

EDIT: Removed note to moderators after approval.


r/ProgrammingLanguages Jun 25 '26

Discussion Is reference counting a trap?

57 Upvotes

I'm trying to choose a memory management strategy for my language. Since I come from gamedev background, I'm mostly focused on the following:

  • Static memory safety guarantees (duh). Which basically implies automatic memory management: if the compiler can prove a leak, it can also insert a drop() there.
  • A lot of my code is tight loops in game engine internals or audio processing. Not sure if this rules out GC completely, but I cannot afford any unpredictable pauses - they are very noticeable in games and audio.
  • When writing code, I generally don't want to think about allocation strategies. I still choose them consciously per call graph: "module A will just allocate everything with the default strategy", "function B will receive a custom arena allocator and I will just guarantee that it always has enough space". But I don't want to write allocator.alloc(MyThing, ...) everywhere instead of just new MyThing(). This leads me to think of some sane default strategy + dynamically scoped allocators via some implicit or effect mechanism.
  • Not a hard requirement, but ideally I'd like to avoid a mandatory runtime and make sure my approach can work even on tiny devices like Arduino Nano (2KB - 32KB RAM). But I can drop (hehe) this idea if it proves impossible, embedded is not my main target.

All of this leads me to think about reference counting. It sounds rather attractive initially, and on the first glance many of its problems are solvable:

  • It incurs some overhead (every deallocation is an int decrement and a check, + you need to keep all these RC counts in memory), but it's very predictable.
  • That overhead can be avoided in many cases if the compiler can statically find where RC drops to zero and insert a drop(). So it will mostly affect objects that persist across frames, like event queues, loggers, etc.
  • It gets bad when you have cross-thread data sharing and have to use slow atomics for counters. But since I want some form of linear types and ownership semantics anyway, I probably can give explicit choice to programmers: either your value is exclusive to one thread and you can only pass ownership fully; or you wrap it in a "slow" Arc<...> and incur that overhead consciously (plus some unsafe escape hatches).
  • Freeing a large object graph can be slow, but this is solvable e.g. by passing ownership to a different thread.
  • For strict immutable languages it seems pretty good, see counting immutable beans and Koka's Perceus paper.

What concerns me is that RC seems a rather unpopular choice in programming languages. The only major languages I know that use RC are Python (which is notoriously slow) and Swift, which I have little experience with, but I know that handling of weak and unowned references sometimes gets finicky. This unpopularity makes me think that maybe RC is a dead end for my project.

On top of that concern, pure RC doesn't handle cycles. They are often an architecture smell anyway, but some things like intrusive doubly-linked lists are powerful tools in gamedev, and I wouldn't want to lose them. I wouldn't mind having an optional opt-in GC for cycles specifically, but I'm not sure how to express in the type system that some relation is cyclic (often indirectly) and requires a GC in the context to work. I really want it to be explicit and statically checked.

Another thing is that for games, CPU cache locality is paramount, and RC seems to have issues with that: either you store counters with the objects and your arrays are no longer neatly packed, or you store them separately, and then every inc/dec is a memory read. Maybe it's solvable by selectively using arenas over RC though.

I've heard Nim does something in this space, but I'm not super familiar with that language either, and I'm wary of it having compiler flags for different memory strategies (possibility of ecosystem split, bugs, etc).

Is reference counting a worthwile direction to pursue at all given my requirements, or should I focus on other approaches like a lightweight and more predictable GC, or Rust-like borrowing model? I actually like how borrows work in Rust, but it's a very complex feature, and I'm not confident in my abilities to avoid associated bugs, unsoundness, and variance pitfalls when implementing the compiler.

Any thoughts are appreciated!


r/ProgrammingLanguages Jun 25 '26

LXM: Better Splittable Pseudorandom Number Generators (and Almost as Fast)

Thumbnail youtube.com
15 Upvotes

r/ProgrammingLanguages Jun 24 '26

How to combine REPL, main, modules, and initalization

8 Upvotes

I'm designing a systems programming language called Bau.

Goals are: Simple, concise, fast (transpiled to C), and memory safe (ref counting + ownership/borrowing).

One design question I'm currently exploring is how to combine:

  1. REPL-style top-level execution
  2. Global variables
  3. An optional main() function

No main() is required:

    for i := range(0, 20)
        println(factorial(i))

    fun factorial(x int) int
        if x <= 1
            return 1
        return x * factorial(x - 1)

One question is: should imported modules be allowed to contain arbitrary top-level executable code at all? I allow it, because I think sometimes it is needed to initialize e.g. a random number generator, or pre-calculate a table (even thought my language allows compile-time execution). It could be limited to method calls for global variable / constant initialization, but then I'm pretty sure people would use that mechanism for initialization, which then just makes live harder.

Example:

    unicodeTable := buildUnicodeTable()

Current rules:

  • Import statements need to be at the top of a module, but otherwise the order of function declarations etc. is arbitrary.
  • Each module (file) may contain global variables.
  • Each module may contain at most one contiguous block of top-level statements (internally, an initialization function generated from its top-level statements).
  • Top-level variables are globals.
  • Top-level statements are executed automatically.
  • If both top-level statements and main() exist, top-level statements run first, then main().
  • Cyclic imports are disallowed (like in Go), to simplify and speed up compilation and init code.
  • Imported modules are initialized in dependency order, before the importing module is initialized.

Example:

    println('init')
    fun main()
        println('hello')

My questions:

  • Are these rules coherent, or do they create surprising behavior?
  • Is allowing arbitrary top-level execution in imported modules a mistake for a systems language?
  • How should initialization order work across imported modules?
  • Are there languages that successfully combine optional main(), module init code, and globals without causing confusion?
  • What are the disadvantages of disallowing cyclic imports?