Tuesday, July 3, 2018

A one word change to the C standard to make undefined behavior sane again

A lot has been written on C undefined behavior, some of it by myself and a lot more by people who know a lot more about compilers than I do. However, I now believe that a seemingly innocuous but far-reaching change to the standard has given permission for the current craziness, and I think undoing that change could be a start in rectifying the situation.

Proposal

In section 3.4.3, change the word "possible" back to "permissible", the way it was in C89.

Background

In all versions of the standard I have checked, section 3.4.3 defines the term "undefined behavior".
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

So that seems pretty clear, the compiler can do whatever it wants. But wait, there is a second paragraph that clarifies:

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
So it's not a free-for-all, in fact it is pretty clear about what the compiler is and is not allowed to do, as there are essentially three options:
  1. It "ignores" the situation completely, so if the CPU hardware produces an overflow or underflow on an arithmetic operation, well that's what you get. If you write to a string constant, the compiler emits the write and either the string constant might get changed if there is no memory protection for string constants or you might get a segfault if there is.
  2. It "behaves in a manner characteristic of the environment". So no "demons flying out of your nose" nonsense, and no arbitrary transformations of programs. And whatever you do, you have to document it, though you are not required to print a diagnostic.
  3. It can terminate with an error message.
I would suggest that current behavior is not one of these three, and it's not in the range bounded by these three either. It is clearly outside that defined range of "permissible" undefined behavior.

But of course compiler writers have an out, because more recent versions of the standard changed the word "permissible", which clearly restricts what you are allowed to do, to "possible", which means this is just an illustration of what might happen.

So let's change the word back to "permissible".

Saturday, April 21, 2018

Even Simpler Notifications: Notification Messages

The Notification Protocols implementation I showed yesterday is actually just one in a series of implementations that came out of thinking about notifications.

The basic insight, and it is a pretty trivial one, is that a notification is just a broadcast message. So when we have real messaging, which we do, notifications consist of sending a message to a dynamically defined set of receivers.

How can we send a message, for example -modelDidChange: to a set of receivers? With HOM that's also trivial:


[[receivers do] modelDidChange:aPath];

So how do we define the set of receivers? Well, the collection is defined as all the objects listening to a particular notification, in this case this notification is the message. So instead of do, we need a notify: HOM that collects all the receivers registered for that message:


[[center notifyListenersOf:@selector(modelDidChange:)] modelDidChange:aPath];

But of course this is redundant, we already have the modelDidChange: message in the argument message of the HOM. So we can remove the argument from the notify HOM:


[[center notify] modelDidChange:aPath];

A trivial notification center now is just a dictionary of arrays of receivers, with the keys of the dictionary being the message names. With that dictionary called receiversByNotificationName, the notify HOM would look as follows:
DEFINE_HOM(notify, void)
{
    NSString *key = NSStringFromSelector([invocation selector]);
    NSArray *receivers = self.receiversByNotificationName[key];
    [[invocation do] invokeWithTarget:[receivers each]];
}

Of course, you can also integrate with NSNotificationCenter by using the converted message name as the NSNotification name.

You would then register for the notification with the following code:


-(void)registerNotificationMessage:(SEL)aMessage
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:aMessage name:NSStringFromSelector(aMessage) object:nil];
}

The HOM code is a little more involved, I'll leave that as an exercise for the reader.

So, by taking messaging seriously, we can get elegant notifications in even less code than Notification Protocols. The difference is that Notification Protocols actually let you declare adoption of a notification statically in your class's interface, and have that declaration be meaningful.


@interface MyGreatView : NSView <ModelDidChangeNotification>

Another aspect is that while HOM is almost certainly too much for poor old Swift to handle, it should be capable of dealing with Notification Protocols. Watch this space!

Friday, April 20, 2018

Notification Protocols

NSNotificationCenter is straightforward mechanism for implementing a clean MVC architecture, but it is also somewhat cumbersome and error-prone. The receiving object has to register itself with the notification center for the particular notification, specified by a string. It also has to implement a method for handling the notification, and the registration and the method have to match. The string used has to be coordinated with the senders of the notification, with no checking whatsoever. The compiler doesn't help with the manual bookkeeping and there is no indication in the interface that the class receives notifications.

Let's fix this with Notification Protocols.

Notification Protocols: Usage

First, let's define a protocol for our notification. We want a ModelDidChange notification. We also want common message to be sent to our objects, in this case the message -modelDidChange:.
@protocol ModelDidChange <MPWNotificationProtocol>

-(void)modelDidChange:(NSNotifiction*)notification;

@end

This protocol must contain a single message and conform to MPWNotificationProtocol, which indicates that this is a notification protocol. This way, we define that a particular notification always maps to one specific message, but this is common and should probably be considered a best-practice.

Second, a class wishing to adopt this notification must conform to the protocol:


@interface NotifiedView:NSView <ModelDidChange>

@end

NotifiedView must implement -(void)modelDidChange:(NSNotificaiton*)notification;, but fortunately the compiler will tell us if we forget to do this, because of the protocol conformance declaration. Finally, and this is the part that can't really be checked, the object must call [self installProtocolNotifications] somewhere in its initializer. It's probably best to do this in a common superclass.

Then, just use the PROTOCOL_NOTIFY macro to send the notification. The macro takes the name of the protocol as its argument. It uses the @protocol compiler directive to turn that into the protocol and then uses the protocol's name for the notification.


PROTOCOL_NOTIFY(ModelDidChange,changedUri);

That's it! Your NotifiedView will now get the -modelDidChange: message. The string is hidden behind the use of @protocol, meaning the compiler helps a bit in checking that we are sending the right notification (there are far fewer protocols than strings). The protocol also helps us keep the mapping from notification to message straight, mostly by automating it away. It also allows us to declare conformance to the notification statically, both for readers and for checking that we actually implement the method.

Notification Protocols: Implementation

The implementation is quite simple: the -installProtocolNotifications method iterates over all the protocols an object's class conforms to. For the ones that conform to MPWNotificationProtocol it registers itself with NSotificationCenter to be sent the message in the protocol.

The implementation is part of MPWFoundation.

Saturday, January 13, 2018

Meltdown patch reduces mkfile(8) throughput to less than 1/3 on macOS

In a previous post, I noted that mkfile was severely syscall limited on OSX due to too small transaction sizes, managing only around 250MB/s on the built-in SSD hardware that could achieve 2GB/s with larger buffer size.

I just retested the stock mkfile after the meltdown patch, and I/O rates are now down to a measly 61.5MB/s, measured both by wall-clock (well, time) and iostat. That's actually 1/4 the throughtput measured before, but the new timing is also with APFS enabled. Using large buffers to minimize the number of sys calls and presumably effectively eliminates the meltdown penalty shows the maximum throughput with APFS to be reduced by about 20% compared to before, to 1.6GBs. Just to put that graphically, here's disk throughput pre-APFS/Meltdown and post, using either very large block sizes (1MB) or the 512 byte block sizes used by mkfile:

So more than ever: use (really) large batches for I/O!