Remix.run Logo
mrkeen 2 days ago

No. Type systems are unrelated to abstract machines which are unrelated to usability.

Type inference/checking happens early in the pipeline.

SSA is a way of laying out assembly instructions for an abstract machine. I say abstract because real machines re-assign values to the same addresses over time (which is precisely what 'single' static assignment prescribes against). Once you know which registers your real machine has (and instructions), you could take your SSA and turn it into real assembly.

Also, "single-pass SSA"? Not to be too pedantic, but SSA is the destination, not the journey. You could take a single pass to transform from some expressions or statements into SSA, or perhaps from SSA into something else. What's the paper?

buybackoff 2 days ago | parent | next [-]

My idea was that with single pass, I can build SSA form during AST construction, and use phi-nodes to update type flow info. Then I could use SSA form to prove that I can use certain optimized bytecode instructions when a variable/register is known to be of certain type (I have virtual registers and fat instructions, eg ADD takes 2 sources and destination). Maybe I'm mixing control flow, type flow and SSA. I do not understand where I should stop with the pipeline if I use bytecode/VM.

The paper is: Brandis, Marc M., and Hanspeter Mössenböck. "Single-pass generation of static single-assignment form for structured languages." (https://bernsteinbear.com/assets/img/brandis-single-pass.pdf). It was quite understandable to me. For a deeper dive with proper SSA construction with dominance frontiers I could not find time to dig deeper, many other papers on SSA require focused CS work on them, not practically feasible for a side project. Also, single-pass is a requirement for very fast compilation to bytecode and LSP feedback.

I tried to read TS and Pyright source code, they share the same style of immense files and nested local functions, that was quite a steep wall to understand actual inner workings in detail. Maybe TS implementation in Go will be easier to read, it's on my later TODO list. It's tempting to use AI for help, but I'm quite experienced already with undoing AI work when it takes a wrong direction and I do not notice early.

mrkeen 2 days ago | parent [-]

Yep, this sounds like conflating two different ideas about SSA.

You could parse a source language with shadowed variables into an AST, and then one of your earliest AST transforms could be a 'de-shadowing' pass. The resulting AST would only see variables assigned only once.

Then a type-inference pass, where your AST expressions would gain type info.

(Then a bunch more passes, e.g. closure conversion if you have them)

Then towards the end you could lower your typed AST into a typed instruction list (having the SSA property - but nothing to do with allowing variables and their types to shadow earlier in the pipeline)

buybackoff a day ago | parent [-]

Shadowing at AST level with the lexical scope is easy to implement, it's just each usage looks up inside out to parent scopes. But if we treat each assignment as a kind of shadowing, it works in a similar way and turns into a kind of SSA. The complexity arises with phi-nodes when multiple paths join. I think the confusion comes from the strict definition of SSA as something useful for the very late stage in the pipeline, but the same concept can exist much earlier in the pipeline.

mrkeen 2 days ago | parent | prev [-]

Ok. More thoughts.

I was trying to see what was special about Crystal in this regard.

It seems like if you took any ML or Haskell-like, you'd have type inference.

Then you could allow shadowing (Rust-style) meaning the same symbol in the source code would be one variable now, and a different variable later.

Then your compiler would need to distinguish x into x1 and x2 so it could track them separately.

So yeah, kind of an SSA I guess!

buybackoff 2 days ago | parent [-]

Yes, a lexical scope with shadowing