For over a year now, I've had rattling around in my head the idea that DeclarativeProgramming - in particular, ConstraintLogicProgramming or a strict form of GoalBasedProgramming, would make for some very interesting implementation of DeviceDrivers for computer OperatingSystems and bare-metal programming. This idea turns on its head the accepted 'fact' that low-level driver code ought to be written in low-level programming language.
A DeclarativeDeviceDriver is an instance of DeclarativeMetaprogramming.
The basic approach, given ConstraintLogicProgramming as the basis (since its use has been proven) is to:
- (1) create a database describing the potential system hardware that might receive interactions.
- This database essentially constitutes the working hardware set for the OperatingSystem, and is continuously refined in a global community sense (or possibly usurped by the hardware makers).
- This database ought to be extensible, such that new hardware specifications can easily be added to it.
- Hardware specifications include interfaces, capabilities, timing information, heat and energy costs, probabilities of error or failure, et cetera.
- Hardware specifications include semantics - in this case the consequences of acting in a certain way, et cetera.
- This database happens to include the CPUs and GPUs, L1 and L2 caches, Network Cards, and everything else.
- (2) Create a database describing the actual system hardware and circumstances in terms of references to the 'global' hardware database.
- This database should be much, MUCH smaller than the first. It corresponds to selecting the drivers for a kernel for one machine (or one family of cloned machines).
- This database can also specify potentials for removal or addition of hardware (or even the destruction of hardware, dealing with military-spec or redundant systems) at runtime, between boots, et cetera.
- (3) Create a mostly independent database describing the policies and services for hardware utilization.
- This database corresponds to the 'kernel' itself.
- It includes facts about how driver services need to interact with the rest of the system:
- what interfaces they need to provide,
- what actions they need to perform in response to certain messages,
- whether drivers need to be registered
- whether the kernel itself is to be 'modular' and replaceable
- HardRealTime and related concerns and constraints: statements of fact regarding the time to deal with certain hardware interrupts, or the amount of memory into which the resulting OperatingSystem (or at least the core services) must fit, or the amount of time to boot.
- It includes policies for handling hardware discovery, error, or loss.
- It includes detailed policies for scheduling, memory distribution, conservation of powe resource distribution and redaction, capability requirements and what it takes to prove them, et cetera.
- At this point, one could choose to be 'modular' where each piece of hardware or category of hardware is a different service, or create one monolithic mega-kernel design where all drivers know about one another, or choose something in-between.
- Optimization decisions go here, as do concerns about HardRealTime, conservation of batteries, reducing heat, et cetera.
- Finally, contains tons of strategies related to these policies: ideas on how one might go about implementing things so that the compiler gets a head start. These might join some common strategies in the hardware database (e.g. mechanisms and costs when using different ways of ensuring atomic operations, variation-strategies between single-processor-shared-memory and multi-processor-shared-memory, et cetera.).
Mechanism: Using
ConstraintLogicProgramming, the basic 'mechanism' is to attempt to unify these databases (mostly the latter two - mechanism and policy - but the second has references to the first) and produce a value that describes resource allocations, procedures for achieving these, procedures that implement the required interfaces, and so on. This will probably take a very long time to do, unfortunately, so a lesser form is to do it on a per-hardware-component rather than on the 'whole' system, and also to improve strategies and heuristics for getting stuff into a 'good enough' state fast (before optimization). Oh, and plus the general idea of an
AssociativeMemory helper-agent that can at least reduce the cost of a edit-compile-test cycle by remembering critical branches when it came to previous proofs.
If one opted for the non-modular approach, the consequence is one gigantic value describing the system and its internal state and its interactions.
After building the initial kernel, parts of the kernel and hardware specifications continue to be used by the compiler and optimizer for UserSpace services. This allows the optimizer to (depending on modularity policy) possibly penetrate abstraction layers for extra optimizations, to eliminate security checks where capability can be proven, et cetera. Also, the same database about the processor is required for optimizations anyway.
Declarative programming would offer some very nice properties in this field:
- Proof of correctness, not only for the individual drivers but for the emergent system, is built right into the driver-construction.
- Hardware Database is always available (and is valuable in its own right)
- It gives free reign to the optimizer, and makes it much easier to use strategies to 'suggest' optimizations that you're unlikely do (or even to think of) in normal cases... for example, using the GPU to do certain types of fast, vector, floating-point operations in parallel with the CPU when it isn't being used for other things. When writing low-level Linux-driver code, this idea would be such a huge hassle that even thinking about doing it could give a systems programmer migraines, would require a great deal of thought about the lack of synchronous operation and state-management, and so on. With declarative code, one merely suggests it (e.g. as a service or optimization), indicates that it can't happen when that part of the GPU is being used for graphics and that graphics have priority (a statement of resource distribution, but also of the fact that the service needs to keep a bit of state regarding the -current- usage of the resource), specifies (in general terms) facts and requirements involving ALL asynchronous operations, and then allows the compiler to get the migraine while the programmer grabs a beer (and doesn't plan on returning before morning, given the likely compile-time).
The disadvantage is also obvious:
DeclarativeProgramming can be fast, but needs to be properly designed to take advantage of strategies and heuristics, and even then it is likely to waste a lot of time thinking about bad solutions... solutions we would
know are bad based on our own
AssociativeMemory (aka 'experience'). Thinking about bad solutions takes a while. Without a highly developed
AssociativeMemory helper-agent that can provide 'learned' heuristics and strategies to the prover across edit-compile-test cycles, it is unlikely that the time required for this operation would ever reduce to a small enough period that tinkerers would join the crowd (running it on their home machines).
As a note, I have listed in my Nov/Dec 2006 composition books that I'm planning to attempt this path when building 'EiraOs?' - it's been a while, and I expect another five years before I'm even ready to really think about Eira seriously, but I suppose that if I'm going to have a VaporWare OperatingSystem I might as well have one with ALL the nice features and cool ideas (like the built-in WikiIde as the primary ObjectSystem ;-). Of course, I don't see much purpose in attempting to clone Linux's efforts, either - if I'm going to go for something new, I want something really new rather than another 'improved' clone of a bad OS.
See Also: DeviceDriver, ConstraintLogicProgramming, DeclarativeMetaprogramming