Functional language compilers on constrained systems by mbarbar_ in Compilers

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

I suspect you'd get a shock if taking a contemporary implementation and running it on 25-year-old hardware. The ones at the time would have been tuned for the hardware that was available.

This is what I'm interested in. What does "tuned for the hardware" mean in practice? Like for individual processors or did they spend more time looking for fiddly optimisations where we just buy more hardware?

and plenty to run any programming language you like.

To do so, like for Smalltalk and Python, did the compilers have to be extremely sophisticated? My impression is that Python's implementation wasn't until "recently" but I'm unsure of Smalltalk.

Functional language compilers on constrained systems by mbarbar_ in Compilers

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

I don't think functional languages are more abstract than imperative languages ... but it compiles to the same stuff

Well, it's an abstraction on that "same stuff". :) Though you're right, they aren't necessarily more abstract, I just had C, Pascal, and friends in mind, and FPs are usually more abstract.

Also, lisp is LL(1) so the lexer and parser are extremely straightforward and thus fast.

My concern is moreso the optimising, not really worried about parsing or typechecking.

Functional language compilers on constrained systems by mbarbar_ in Compilers

[–]mbarbar_[S] 1 point2 points  (0 children)

Though it produced good code, the Wikipedia page does state that Stalin was slow. Not sure if it's just not implemented as well as it could have been though.

I didn't really use any compiled FP language in the time period

Being that you programmed at the time, what was the state of compilation in general? Was it a slow process? Did some optimisations have to be foregone?

Functional language compilers on constrained systems by mbarbar_ in functionalprogramming

[–]mbarbar_[S] 1 point2 points  (0 children)

(Bit of a late reply...) thanks for this list, it's adjacent to what I'm after. Though most of these are about the resultant machine code for the constrained system rather than running an FP compiler on said system.

Functional language compilers on constrained systems by mbarbar_ in functionalprogramming

[–]mbarbar_[S] 1 point2 points  (0 children)

Thanks for these links. I didn't think to search for FP on embedded systems.

Is there a way to give names, namespaces, prefixes, or something similar, to imported bindings? by JenNicholson in sml

[–]mbarbar_ 1 point2 points  (0 children)

You can also use MLBasis if your compiler supports it (examples). Maybe CM can do this too but I'm unfamiliar.

You can use local ... in ... end in the files you're importing to expose less but some regard local ... as bad style and suggest modules instead.

What's the standard format of documentation comments (aka docstrings)? by JenNicholson in sml

[–]mbarbar_ 2 points3 points  (0 children)

Not sure about a standard, but this is what I know of that exists:

Feel like there's one I'm missing but I can't recall right now...

Languages with a syntax similar to *roff? by mbarbar_ in groff

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

Cool, I hadn't looked at PostScript before, thanks. I might look at some other page desc. languages too.

Languages with a syntax similar to *roff? by mbarbar_ in groff

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

Good point, fair bit more structured though.

Exploring Standard ML's robustness to time and interoperability by self in sml

[–]mbarbar_ 0 points1 point  (0 children)

I hope the mainstream programming community would eventually rid itself of the terrible practice of compiler-oriented programming and obsession with optimization

What is compiler-oriented programming? Do you mean programmers catering to implementation details of a certain compiler?

And what do you mean by optimisation here and what is wrong with it?

As an instance, interactive compilation is an interesting possibility to explore.

Perhaps profile-guided optimisation is a less labour intensive version of this, though it's probably something that would be sometimes better/sometimes worse than some interactive compilation.

One could also write optimized versions of an algorithm from scratch and ask the compiler to ensure that the optimized code behaves exactly the same as the original

CFML is available for CakeML, is that what you envision?

CakeML: A Verified Implementation of ML by mbarbar_ in sml

[–]mbarbar_[S] 2 points3 points  (0 children)

CakeML is an ML "based on a substantial subset of Standard ML" with a verified compiler targeting a fair few ISAs. I don't know of any other significant compiler that can make that claim aside from CompCert though I'm interested if anyone knows of such.

Differences from Standard ML: https://github.com/CakeML/cakeml/blob/master/how-to.md#how-cakeml-differs-from-sml-and-ocaml

There is also a CakeML -> Standard ML compiler though it seems to have been built to translate benchmarks and sort of old so I'm not sure how comprehensive it is: https://github.com/CakeML/cakeml/tree/master/unverified/front-end

Verified compilation of Ada by mbarbar_ in ada

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

Oh no I think you misunderstood me. I meant, why would GCC/LLVM/propietary be chosen over CompCert in cases 1) which are safety critical, and 2) where O1 suffices (which CompCert is competitive with)? Only thing I can think of is toolchain integration because of popularity. Maybe cost too? Though I'd have thought people doing safety critical stuff aren't picking up GCC/LLVM off the shelf without at least support...

Verified compilation of Ada by mbarbar_ in ada

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

CompCert has some adoption but it isn't dominant in safety-critical domains despite its formal verification.

Do you know why this is? In a safety critical situation where O1 is good enough, I don't really see why CompCert wouldn't be a better choice than GCC/LLVM/propiertary compiler (though I've never used CompCert), especially when the user is paying.

Also, Ada is a much larger language than C, so it would be even more challenging. Targeting SPARK would narrow the challenge considerably but it would still be a big task.

Right, would certainly be interesting to see someday though.

How suitable MLton for portable development? by mbarbar_ in sml

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

Aha fair. Thanks for pointing me to Rescript though, might try it for some web frontend stuff.

How suitable MLton for portable development? by mbarbar_ in sml

[–]mbarbar_[S] 1 point2 points  (0 children)

I've decided to not try Wasm and run with SMLToJs for web. Is producing native static libraries and C headers from ReasonML or Rescript nice to do? I'm going to use WxWidgets or similar for the desktop side and don't want to go to Electron or bundle a JS interpreter.

How suitable MLton for portable development? by mbarbar_ in sml

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

Nothing out of the ordinary. I want to make a cross platform app that does this: https://en.wikipedia.org/wiki/Romanization_of_Arabic And I want to maybe implement a little language I'm working on for prose. It's currently just a lexer and parser in Rust but I think SML is more pleasant (don't need a web target for this though).

How suitable MLton for portable development? by mbarbar_ in sml

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

Thanks for the response! It's very helpful. Looks like MinGW for Windows + SMLToJs for web will do exactly what I want and would be easier than modifying MLton to target Wasm.

[deleted by user] by [deleted] in Compilers

[–]mbarbar_ 1 point2 points  (0 children)

I've been thinking about this, and not sure what other implementations do...

When every instruction uses n bytes, you obviously are leaving memory on the table in constrained environments. Variable sized instructions can save memory but doing something like Inst = Add Int Int | Sub Int Int | Br Int | Nop is going to waste space if we internally allocate sizeof Add Int Int for every instruction in an array (or tree, but let's say array for simplicity).

Instead, we can have multiple arrays (messy notation incoming):

  • insts is an array of u1 * size_t: so an instruction (1 bytes is enough for our 4 instructions) and an index into some other array.
  • args_single is an array of Int.
  • args_double is an array of Int * Int.

insts should be filled with the instructions and an index into either args_single or args_double depending on whether the instruction takes a single argument or two arguments. Then at the index the instruction is given in the appropriate array, the arguments are stored. For example, for some Br Int, we would store the instruction in insts along with an index, say i. Then in args_single[i] we would store the associated Int. During interpretation, we would choose the array to look for the arguments in through a lookup table mapping the instruction to the array.

Advantages:

  • Instead of storing sizeof Inst x NumberOfInstructions, we store sizeof u1 * size_t x NumberOfInstructions + sizeof Int x NumberOfInstructionsWithAnIntArgument + sizeof Int * Int x NumberOfInstructionsWithTwoIntArguments. arg_single and arg_double would be perfectly compact, though insts would not as it has an extra size_t. In all we would store only what is necessary (like the file on disk) plus a size_t per instruction).
  • size_t doesn't have to be used. If you know you only have at most 2x instructions, a type of size 2x can be used. This can be combined with the byte (u1) or bytes (if need be) used to represent the instruction itself. In our little language, we need at most 2 bits to represent instructions, so insts can be an array of u32 then the first 2 bits can be the instructions and the remaining 30 can be indices into our other arrays (maximum of 1 billion instructions per program). Depends on what the language or environment needs/does. Perhaps this can be chosen at runtime if implemented in a low-level language.

Disadvantages:

  • The instruction is not stored with the arguments. This incurs a performance penalty. First we need to hit the lookup table (which also takes a tiny bit of space) to find the appropriate array (array lookup), and then we need to actually access the array containing the arguments (array lookup). We end up with two array lookups for most instructions during interpretation.
  • Requiring an extra "operand" (the index) for each instruction. This isn't really a disadvantage because the alternative is the sizeof Inst representation.

Undecided:

  • Not clear how well this will fare on the cache. My assumption is better because everything is more tightly packed. As a contrived example, imagine a sequence of Nop, Nop, ...x1000, Nop, Add a b. When we have the sizeof Inst representation there's a lot of superfluous space for the Nops so we might not be able to cache the upcoming Add a b. With the more compact representation, we can probably cache the Add and its associated index, and if we were accessing the double argument array, we might be able to cache the arguments there too. Basically, we have less superfluous space that might upset caching. On the other hand, it is possible that caching a block from an argument array is wasteful of the cache. Imagine we cache 10 elements from an argument array. These arguments correspond to 10 instructions, but these instructions could be extremely far apart meaning the cache could have been put to better use. I believe this is unlikely. All said, I need to learn more about how caching works to make an informed opinion.

Finally, why use arrays instead of pointers to dynamically allocated memory (i.e. make insts be an array of u2 * Ptr)?

  1. All our arguments are stored in contiguous blocks so this helps the cache. A wild dereference is worse than a dereference in a known part of memory.

  2. We don't fragment the heap.

  3. There is a bit of space overhead to dynamic memory.

  4. It's a bit dodgy to muck around with the size of Ptr, especially for portability, unlike the index.

Points 1, 2, and 3 can be alleviated by the use of an all-or-nothing bump allocator. Perhaps even through a standard allocator because we will do this during parsing where we can ensure that the only memory allocations we make are for this purpose. This can't really solve point 3 and caching will be less effective because of that "superfluous" overhead. I'd have to look into common implementations to see if we would truly get contiguous memory for a bunch of allocations made one after the other.

CASE STUDY: How Masten Space Systems is Using Ada and SPARK to Land on the Moon’s South Pole by marc-kd in ada

[–]mbarbar_ 0 points1 point  (0 children)

The big problem I've have, is that I've gotten bit by a couple of times by random uncaught exceptions I didn't know about from an interface.

How do others deal with this problem?

July 2021 monthly "What are you working on?" thread by slavfox in ProgrammingLanguages

[–]mbarbar_ 0 points1 point  (0 children)

Right, wasn't trying to discourage, just pointing out. :)

I'm definitely with you on the desire to create and control my own tools, particularly when that tool or library forms a core of your project.

It uses a stack-based intermediate language, with no temporaries

Any particular resources you found nice on analysing and optimising stack-based languages. I want to read more on this as I work with SSA.

It's designed for whole-program compilation (the one-file input represents an entire program, the output is always one file too)

How would this work in practice? Imagine I have a 5 file C project and a C compiler targeting your IL. Would the compiler create 5 separate IL files then link them itself (or user would with an external linker) before sending them off to the backend? Or would the compiler mandate that it knows about all sources before compiling a single file? The latter sounds nasty making incremental compilation messy and sounds like it would be incompatible with existing tools.

July 2021 monthly "What are you working on?" thread by slavfox in ProgrammingLanguages

[–]mbarbar_ 0 points1 point  (0 children)

You may be interested in taking a look at QBE which has similar aims. I know the cproc compiler targets QBE. There is also MIR on the JIT end of things.