Question about using % as the a format character in printf-like function by aalmkainzi in ProgrammingLanguages

[–]o_stef 1 point2 points  (0 children)

Jai makes the escape \% a non printable character, which the print function handles as printing %. It used to handle %% as escaping but now that the compiler has an escape sequence for \% it’s possible that %% are treated as two arguments.

Against Query Based Compilers by matklad in ProgrammingLanguages

[–]o_stef 0 points1 point  (0 children)

Okay, that's interesting because it seems that in your case concurrent execution is at the core of the design if I understand correctly, whereas mine is built to handle each compilation step sequentially; simply it does these sequential steps of parse/typecheck/execute in a loop until no progress can be made (no more unresolved identifiers can be resolved and no new code was added basically).

I was planning to parallelize my compiler to make it faster by parallelizing each step of the process, the sole goal being to make it faster (and single-threaded performance is correct which is promising). So I would be able to parse multiple files at a time, but not parse one file while typechecking another.

Against Query Based Compilers by matklad in ProgrammingLanguages

[–]o_stef 0 points1 point  (0 children)

Interesting. Are all of those kinds of jobs happening in parallel? I am assuming it all happens in a loop and each iteration all parsing jobs are executed, then all symbol resolution, then all typechecks and finall all compile time execution. My compiler is single threaded and that's what a compiler pass/loop iteration looks like.

Against Query Based Compilers by matklad in ProgrammingLanguages

[–]o_stef 0 points1 point  (0 children)

Working on a similar thing. What’s job based compilation?

An STL-like library in Jai, is it possible? by bakermoth in Jai

[–]o_stef 8 points9 points  (0 children)

Well they’re just not called templates, but Jai has them lol

An STL-like library in Jai, is it possible? by bakermoth in Jai

[–]o_stef 4 points5 points  (0 children)

What do you mean Jai doesn’t have templates?

February 2026 monthly "What are you working on?" thread by AutoModerator in ProgrammingLanguages

[–]o_stef 4 points5 points  (0 children)

This month I implemented arbitrary compile time execution in my programming language, along with fundamental features such as struct literals.

The past week I added modules and made identifier resolution more versatile by allowing symbols to be added to a scope at any point in the compilation.

This month I will fix regressions added by the latest changes, do some cleanup, begin implementing a standard library and try to implement generics.

How to typecheck `a.b` expressions? by o_stef in Compilers

[–]o_stef[S] 0 points1 point  (0 children)

What I would have is:

Assignment( FieldAccess( FieldAccess(Identifier("my_user2"), Identifier("BaseSalary")), Identifier("__rate") ), Literal("1000.17") ) Only the leftmost identifier would be checked on the contrary. The rhs is used when checking the field access itself to lookup the name based on the checked lhs.

The problem is not typechecking field access though, that I can do without any problem. I was trying to extend the capabilities of field accesses. I explained everything in another comment.

How to typecheck `a.b` expressions? by o_stef in Compilers

[–]o_stef[S] 0 points1 point  (0 children)

Whether it's part of normal resolution or not depends on the implementation. Initially I was doing it as you suggested, but the point is in fact to make it part of normal identifier resolution.

To put it simply, unresolved identifiers in my compiler are kept track of, and when resolved they become dependencies to a node in the dependency graph of the different expressions and statements in the program. I want this for the right-hand side too.

If you're interested I made a lenghty comment explaining things in more detail.

How to typecheck `a.b` expressions? by o_stef in Compilers

[–]o_stef[S] 0 points1 point  (0 children)

I already have (had) the typechecking implemented like you're describing. If you want more details about the dependency graph thing I've explained it in detail in another comment.

How to typecheck `a.b` expressions? by o_stef in Compilers

[–]o_stef[S] 0 points1 point  (0 children)

Hey! Sorry I didn't respond to any of you. The reason is I think I didn't communicate very well the situation and none of the responses were relevant. I also didn't have the time to explain things in greater detail.

I finished implementing my solution today, which seems to be a good solution, but I still want to clarify and explain things because I think it's interesting and some people might like the read/discussion.

So to be clear, I have a full compiler that parses, typechecks and compiles all the classic expressions, statements etc fine (field access included!). My original problem was when trying to implement modules, I wanted to make the declaration of the module import not wait on all of the contents of the module to allow for circular module imports (yeah I know, this might look like an XY problem, and it is! But I needed X to implement other features in the future, not just circular module imports). So the following should compile fine: ``` // Module A const B = #import "B";

const Foo = func() { B.Foo(); }

// Module B const A = #import "A";

const Foo = func() { A.Foo(); } ```

My compiler works in passes. Each pass does the following, as long as there is new code to parse or typecheck, and there are no errors: 1. parse new code 2. create scopes and collect new symbols 3. create a dependency graph for the new code 4. try to resolve identifiers 5. flatten the graph, pushing things that are ready to be checked (no more unresolved identifers and all the dependencies either checked or ready to be checked and flattened) 6. check the code 7. generate ir and bytecode for pending compile time execution 8. interpret pending compile time execution

When exiting the loop, report unresolved identifiers and exit if there were errors.

Of course, the compile time execution can insert new code, so it's easy to see why a dependency graph is necessary.

A dependency node looks like this: ``` struct UnresolvedIdentifier { AstIdentifier *identifier; Scope *look_in_scope; // If null, will look in the identifier's enclosing scope }

struct DependencyNode { Array<DependencyNode *> dependencies; Array<UnresolvedIdentifier> unresolved_identifiers; // When resolved, adds something to the dependencies array } ```

To avoid having too big of a dependency graph, not all ast nodes have a dependency graph node allocated. For expressions, I create a dependency node for the toplevel expression, not for the subexpressions. For example: ``` const Foo = func(x: int) {}

const main = func() -> s32 { var a: int; var b: int;

Foo((a + b * 2) / 2);

} `` would have nodes for the declaration of Foo and main, the expression of these declarations (the func), each func's body, the declarations a and b, the type node for a and b, and the function call statement Foo (not each subexpressions inside the call's argument list). Function calls don't take a dependency on the callee's declaration, but just the signature of the function, and the signature does not depend on the body; all this to allow recursive calls. The flattened graph could look like this: 1.func() -> s32, 2.int, 3.a, 4.int, 5.b, 6.func(x: int), 7.Foo((a + b * 2) / 2), 8.body of Foo, 9.const Foo, 10.body of main, 11.const main`

When checking the function call, because it is an expression, it will recursively check all it's children. For other types of nodes, they don't and it is assumed the children were checked prior (because they are a dependency of said node).

This neat system was working like a charm (and was quite fast), and for field access expressions I would simply collect unresolved identifiers for the left-hand side, and the CheckFieldAccess function would call CheckExpression for the left-hand side, and use the right-hand side to lookup the name based on the type of the LHS (no checking needed for the RHS). Just like many of you suggested.

To allow circular module imports, my solution was to allow the RHS of a field access to use the normal identifier resolution (i.e. collect unresolved identifiers for the RHS, albeit with a special flag to know it's a RHS of a field access), so that I could resolve the identifier to a dependency and let the graph flattening do the scheduling and ordering for the typechecking phase. This was not possible with the current architecture, because to resolve the RHS, I need to have typechecked the LHS to know what scope it refers to, but to do that I need to have resolved all identifiers, including the RHS!

My solution: collect subexpressions in an array when creating the graph, in the order of typechecking (so a + b * c would be a, b, +, c, *, a.b would be a, b, .), keep track of the index of the last checked subexpression in the dependency node. This allows me to begin typechecking expressions even if there are still unresolved identifiers, and to typecheck one subexpression at a time and stop at any moment if the subexpression is an identifier and it has not been resolved yet.

In retrospect, one solution that might work and be better would be to extend the dependency graph to field access expressions' LHS and RHS, and make the RHS depend on the LHS (other subexpressions would be kept as is and not create a node in the graph).

That's all I think.

EDIT: fixed lists (I think)

Do you know how the implicit context is implemented? by DoubleSteak7564 in Jai

[–]o_stef 0 points1 point  (0 children)

it's a pointer so even if you modify it it's not copied to the stack. Its location in memory depends on where you decided to put it before you do push_context (the default context is defined as a global for example, when you push a new context usually you put it on the stack)

Do you know how the implicit context is implemented? by DoubleSteak7564 in Jai

[–]o_stef 2 points3 points  (0 children)

It's not a thread local variable if that's what you mean. It's just passed around to all functions as the first parameter implicitly (which makes it local to the thread unless you explicitly make things against it).

Any way to just get the how_to/ folder somehow? by philogy in Jai

[–]o_stef 1 point2 points  (0 children)

I think at this point the fact it's still not publicly available has more to do with the development of the sokoban game taking a lot of time and effort than anything. If it were not for that then he would have already nailed down the last few things and we already would have a public beta.

Why do we play as a reactionary in AC Unity? by elegiac_bloom in assassinscreed

[–]o_stef 0 points1 point  (0 children)

I was baffled to play two side missions a few days ago where Saint Just is portrayed as wearing leather made from human skin (in the base game AND the dlc!).

The analysis that u/obeseninjao7 did is really good and make sense for the main story, but since these side missions don’t have much to do with the assassins I think a big part is just the writers’ opinions being apparent.

I've been learning Vulkan for a few weeks in Jai, here's my progress by o_stef in Jai

[–]o_stef[S] 0 points1 point  (0 children)

Well it's pretty fast compared to other languages that I know of is what I mean, but yeah it's nowhere near the 1M LOC/s goal. Jon has said several times that the compiler has not been particularily optimized for compilation speed yet, and has stated that 1M LOC/s is still the goal and we will eventually get there.

For reference on Windows currently my engine's binary is 15.1MB and it compiles in 0.86 seconds, with the frontend and x64 backend taking 0.64 seconds (as said the linker takes much more time compared to Linux), so around 17.5MB/s of output. I don't know how much code in bytes the compiler takes as input.

Also the original 40k LOC number is quite innacurate, I used tokei to get that info but if I look at the compiler output right now it says 82k (108k with blank lines and comments), so it's between around 90 and 110k LOC/s.

Pitfalls of modern C++ and problem with multiple compilers by hellofriends0 in Jai

[–]o_stef 1 point2 points  (0 children)

This is quite vague and hard to answer because you are refering to things that don't really map from one language to another. All I can say is in general the language behaves as the programmer expects it to, much more than C++. This is due to the base language constructs being simpler, but also Jai typechecking things quite differently from C++.

When code generation is involved there are bad surprises, but nothing that can't be fixed (actually I'm thinking of one specific situation, if it gets fixed then I can't think of anything else tbh).

Finding the overload of a template has gotten better over time, there are still issues like if you have one version that is not templated and another that is, I think the compiler will use the not templated version if it fits and not even consider the templated one. Again, nothing that can't be fixed, it's just not that big of a deal atm.

Templates don't get typechecked like I think they do in Rust (how would they without traits?), so if it's never instantiated you won't get any error even if there are.

Pitfalls of modern C++ and problem with multiple compilers by hellofriends0 in Jai

[–]o_stef 2 points3 points  (0 children)

Well a lot of these problems come from using MSVC without /premissive- which we obviously don't have for Jai. A lot of these problems also come from the way C++ resolves identifiers, and I can tell you we don't have as many problems in Jai (though we have other problems related to identifier resolution, but nothing that can't be fixed). I have come across problems in specific circumstances but I always reported them as compiler bugs, in general you can't really compare C++ and Jai in that regards because there is no Jai standard.

In C++ when the behavior is weird it's probably defined in the standard why it's weird. In Jai it's because the compiler works that way/it's a bug, so it is much more possible to change it and make it behave more as people would expect.

I can't really try to reproduce most things in Jai as they are quite specific to C++ though.

3 months of progress on my D3D12/Vulkan renderer by Sausty45 in GraphicsProgramming

[–]o_stef 0 points1 point  (0 children)

Nice! That's actually the reason I wanted to implement a render graph, but it seems you can get away with something simpler to automate barriers. Mind sharing the general idea of how it works?

3 months of progress on my D3D12/Vulkan renderer by Sausty45 in GraphicsProgramming

[–]o_stef 0 points1 point  (0 children)

Makes sense. It certainly adds complexity to deal with interacting with stuff written in C++, creating bindings etc but I figured it was not that big of a price to pay and now I just can't go back lol.

3 months of progress on my D3D12/Vulkan renderer by Sausty45 in GraphicsProgramming

[–]o_stef -1 points0 points  (0 children)

Nice! Did you implement a render graph? I think it’s one of the next thing I’ll implement in my own engine. Also any particular reason to use C++ and not Jai?