HN user

dumael

81 karma

Garbage collector and compiler programmer.

Ask me about GCs :)

Posts0
Comments47
View on HN
No posts found.

An aside but "Making Money" by Terry Pratchett references a similar sub-office of the Post Office in Ankh-Morpork--the Blind Letter Office. Amusingly, Lord Vetinari is able to assist with some as their addresses are mis-spelled vague directions rather than strict addresses.

The Evening Standard (https://www.standard.co.uk/news/uk/rwanda-asylum-deal-uk-asy...) quotes 200 people from the Rwandan Government, "but that it could be scaled up".

A research briefing document from the House Of Common Library (https://researchbriefings.files.parliament.uk/documents/CBP-...) states that the Memorandum of Understanding on which the scheme relies states no upper limit on the amount of people that can be sent to Rwanda.

rescue.org (https://www.rescue.org/uk/article/why-uk-government-should-r... section 5) reports GBP 140m so far, GBP 120m on up front costs along with The Times reporting a further payment of GBP 20m.

The Migration Observatory (https://migrationobservatory.ox.ac.uk/resources/commentaries... costs section) also cites the GBP 120m figure, along with a citation of Select committee evidence that there will be further per-person costs to HMG.

'signed' and 'unsigned' on their own--act as short-hand for 'signed int' and 'unsigned int' in C and C++.

Note that the size of an 'int' is dependant on the "data model"[1]. As for whether it's the most optimal is far too context dependant.

A data model that defaults 'int's to 32 bits on today's (and yesterday's) architectures is fine in many cases as the range of that type is acceptable for most usages without excessive wastage.

Certain data models do specify that 'int' is 64 bits which can break some programmer's assumptions, and also lead to space wastage as a struct member or a stack slot has to have 64 bits allocated for it on paper.

Data models are part of the ABI your program uses, so it's not necessarily optimal for any given system.

[1] Wikipedia has a table summarizing some of the differences: https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_m...

This is a great overview. I don't remember having to put in padding instructions to prevent the pipeline issues mentioned here; maybe we just never ran into that. (I wrote pretty much all the R3000 code for Crash 1 and just do not recall problems like that coming up.)

If you were using the GNU assembler, it automatically fills branch delay slots with nop instructions unless you prefix assembly code as using `.set noreorder`. GAS would also handle load delay slots as well.

MIPS has what's called an Application Specific Extension (ASE) basis for extending a given MIPS core for particular areas.

The MIPS DSP ASE extends the base instruction set with certain instructions applicable to various codecs of the day that the ASE was defined for. It's essentially extending a general purpose cpu to efficiently perform DSP like tasks.

It would be more likely that nanoMIPS would be considered for open sourcing if that the implementation was to be made open source. Otherwise it would be the fobbing off of releasing the code to the InterAptiv which has MIPS16e(2) support.

MIPSR6 does away with the HI/LO registers and have multiplication instructions which return the result to GPR registers.

Pre-R6 MIPS cores have the MUL instruction which hide the usage of the HI/LO registers, but do clobber those registers.

nanoMIPS doesn't have delay slots IIRC. microMIPSR6 also deprecated delay slots for branches. MIPSR6 got rid of delay slots for a family of branches called 'compact branches' which have 'forbidden slots' which require that back-to-back branches be separated by a nop or other instruction.

GC write barriers are implemented by expanding sequences that update pointers to perform some sort of additional action.

Their purpose is to capture some information about the updated pointer that the GC can then use to avoid a full heap scan. Card marking marks a 'region' as dirty (such as 128/256/512 bytes of memory). This buffer recording the dirty/clean areas is rescanned as part of the evacuation of the generation being collected. Any pointers to the generation then being collected are updated.

Sequential store buffers (SSBs) can be used to record the address of the object being updated or a pointer to the pointer itself. Again rescanned during collection. SSBs can be easily thread-local avoiding the need for thread synchronisation, except during a collection cycle which already requires thread synchronisation.

Write barrier tend to be optimised heavily as they're used quite frequently. The use of atomic barriers or branch instructions (barring fast path exits) would inhibit performance.

Almost every modern compiler uses some form of intermediate representation. The choice of IR is shaped by history and design. As the posted article shows, LLVM uses a SSA based IR to describe programs. GCC in contrast uses 2 IRs, GIMPLE and a LISP based IR called RTL. GHC uses Core Haskell (Haskell without the syntactic sugar).

The purpose of every IR is to remove the ambiguities and language complexities of programs. By simplifying programs into series of statements such as "%3 = op $type %1, %2", generic optimisers can be built easily. Certain language specific optimizations can be written for the frontend of the compiler as they have knowledge of the language being compiled. Generic LLVM-IR may not be optimised to deal with issues such as devirtualization in C++ (though there is work being done in that area).

LLVM's IR undergoes fairly occurrent changes to better handle "new" problems.

It's pretty cool that clang uses this when it knows the value in the first argument is byte-sized.

Clang is using the 8 bit subregister due to how it legalizes types.

When LLVM-IR is compiled for a target, it undergoes a process called "legalization" where invalid operations for the target are Expanded (replace the invalid operation with a semantically equivalent but legal series of operations), or Promoted (e.g. promote operations on boolean types to character types), Libcall (call out to the likes of libgcc.a) or Legal where the target directly supports the operation.

Since X86(_64) supports 8, 16, 32 (and 64) bit register accesses and operations, operations on variables of those sizes will be matched to the corresponding operation and register sizes.

If you were to compile that code for the likes of MIPS, ARM or PowerPC you'd see fully 32 bit code.

An aside:

Tolpin and Toft designed an extension to the functional language ML which used region based memory managed instead of traditional garbage collection for ML.

This lifted the lifetimes of variables into ML's type system (!!!) while the underlying implementation IIRC could achieve O(1) memory behaviour except when an exception occurred.

While this sounds amazing, there were draw-backs on the implementation / theory as certain optimisations were near necessary to get good performance. I.E. word sized integers had to live in the heap as opposed to registers. Another issue was that loops had to be restructured from idiomatic ML style to a slightly different one, other the region inference logic would cause O(N) allocations in a loop which would otherwise use O(1) allocations.

http://www.elsman.com/pdf/retro.pdf

or "Tauplin and Toft region based memory management retrospective" should lead you to the paper.

At the time Intel had already introduced some mobile silicon, but there was little uptake. So they were iterating; they wanted to improve for each succeeding generation. But they had a kind of design-by-committee process. One person or group wanted a certain feature, another group wanted something else, a third group though that yet another thing was important. And so on. Sorry if that sounds vague, I won't write anything more specific.

The end result was a chipset that had a lot of features. A LOT OF FEATURES. Gold plated features. But that meant higher power consumption than the competition, higher cost, larger form factor, longer time to market.

I have some experience in this field, and this sounds utterly bizarre. Most of the customers are fairly selective in what bits they want, so provided everything (including the kitchen sink) in a product is useless.

Being able to comfortably ship any variant of your SoC without certain parts is important.

One option in GCC I'm aware of is -faggressive-loop-optimizations . Quoting the manual "This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes that loop code does not invoke undefined behavior by for example causing signed integer overflows or out-of-bound array accesses. The bounds for the number of iterations of a loop are used to guide loop unrolling and peeling and loop exit test optimizations. This option is enabled by default. "

https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

As I know, some of those contributors are just making systemic changes throughout LLVM, which would require touching the PowerPC backend.

I'm not at all surprised at the @[a-z.]+.ibm.com contributors, the @anl.gov is the code maintainer and highly visible contributor. The Argonne National Laboratory run a PowerPC based supercomputer afaik.

I absolutely prefer strict typing myself. The issue becomes whether or not one thinks type coercion should be a thing.

As a strong typing fanatic, I'd rather have the interpreter/compiler scream at me. In my opinion automatic type coercion is slightly less wrong than undefined behavior in C.

(Aside, did you know that if GCC spots a statically provable null pointer deference, it will insert __builtin_trap() after the deference? I didn't know either until yesterday. This is acceptable because undefined behavior can mean "eat you hard drive".)

If for strings '+' is concatenation, and '*' is for replication with '-' and "/" as exception behavior I can fully understand. (Admittedly '+' over '&' is either style or hard-core "the operation is different, therefore we need a different symbol choice").

Your example worries me. Personally I would not design a language like that.

(Though I may have abused it in the past.)

I'll freely admit I'm not a fan of dynamic languages (mostly due to hacking the Erlang HiPE compiler) and in a previous life I was a Haskell guy. So I'm a fan of strict typing.

The unreasonable behavior is giving integers as strings.

That I can agree with. But in that case this notion of reusing '+' as string concatenation but '-' is coercing strings to integers is utterly bizarre.

I'm afraid to ask what '*' does (string replication?) and '/' "space magic"?

As a compiler developer I honestly shudder reading this. From the last example:

$ node

"5" + "1"

'51'

"5" - "1"

4

How is this in anyway reasonable behavior? '+' is string concatenation but '-' is interpret these strings as numbers and perform arithmetic?

Does the principle of least surprise apply here?

Linking several hundred objects in a dozen or so libaries out of 2.5kish objects together is going to get hairily expensive memory wise no matter what way you do it.

Compiling with debug information embedded raises that to around 12GB to do it in a practical timeframe, though it may have dropped recently with some tweaks that have been published on llvmdev. Unless you've willing to wait a few hours.

GCC is no slouch in that area either but its usage spikes feel lower.

(Source: compiler dev)

Yeah, "new" languages have a real tendency to go for "some sort" of automatic memory management. And they pick the easy option.

Unfortunately, this can lead to wired-in assumptions, such as reference counting which can lead to extension libraries relying on header files to do reference counting logic. The better option, imho would be to instead require extension libraries to 'pin' objects because they've been passed though an FFI.

If a language implementation were to provide an opaque interface of "I am holding onto this object", "I want to allocate an object", "I want to read/write this object's element fields", a far better GC can be provided at a later date while retaining backwards compatibility.

Why go through all this hassle for extension libraries? Because then your GC implementation is completely decoupled from libraries. And most language implementors sufficiently well read to know how to apply GC, but a little ignorant of providing space for a complete decoupling of GC.

Turn GC into an interface and you can provide very advanced GCs if you can find someone to write them. E.g. Java's G1GC for ruby.

Garbage collection worries about reachability of objects, not of access patterns. Some GC setups may not apply read/write barriers to stack objects as they are not required.

The difficultly with stack centric working is that you have to ensure that any object produced that is put back into the heap only references heap objects. Otherwise, by any sane language/style definition, you've made a mistake in referencing stack objects from the heap.

Accesses to the stack or heap, except in very specific circumstances[1] are equal but allocation/deallocation process are widely different.

Avoiding introducing GC work has/(can have) the tendency to "fight the language" problems. Consider writing Haskell code that does not allocate on the heap for example, it's damn near impossible. Otoh a Java program can shift some heap allocations into stack allocations.

[1] Afaik, some intel processors have small dedicated D-caches from stack relative load/stores. On the order of 128 bytes. I am probably wrong though.