I managed to get this with "no OS" † and no standard library / runtime!
Also it is without using built-ins or "import Swift".
Not ready to show the full source yet but here are some bullet points:
- to emulate "no OS" I'm using a simple mac "host" app whose only purpose is to provide 640*120 bits of memory and visualise them.
- without standard library or "import swift" I started from scratch: there's no Int, no Bool, no "==", no math, etc. With this clean state I started building up:
- enum Bool
- Byte (or rather UInt8) is emulated as enumeration as well (with 256 fancy named "xYZ" constants).
- there is no things like Unsafe[Mutable][Raw][Buffer]Pointer in this environment so I created a minimal Pointer with
pointee
property and "+" operation - will do for now.
- no precedence groups are available in this environment (like AssignmentPrecedence, DefaultPrecedence, etc) - so I declared those.
- ditto for the operator declarations (
prefix operator !
, infix operator ==
, etc) - declared those
- no MemoryLayout luxury - defined a protocol with a static "fixedSize" property and make all types adopting that.
- dynamic memory is not available yet (we'll see), and I decided to use Tuples. Introduced a couple of Tuple types.
- Made a few fixed sized arrays (based on Tuples): Array8, Array16, Array128. In essence Array is a tuple with an added "var count" field (as although it fixed it's size is flexible within the capacity) and a few extra methods.
- No Int was available - but here you are: defined UInt64 as a (now available)
Tuple8<UInt8>
, and for now I defined Int to be UInt64 (later on will do something about negatives).
- last but not least. For now decided to tunnel a few things through C. Without that library code would be slower, larger and quite silly, imagine I had to do this:
extension Tuple {
subscribe(_ at: Int) -> T {
get {
switch index {
case 0: return elements.0
case 1: return elements.1
case 2: return elements.2
....
case 255: return elements.255
}
set {
switch index {
case 0: elements.0 = newValue
case 1: elements.1 = newValue
case 2: elements.2 = newValue
....
case 255: elements.255 = newValue
}
}
}
It is still possible to do without C and I want to play with it to have a "not so fast but pure swift with no C dependency" mode. Guess building my own + and *, etc operations from bits all the way up would be fun. 
Anyway, here's the gist of what's tunnelled through C at the moment:
enum SysOp {
case intAdd // +
case intSub // -
case intMul // *
case intLess // <
case compareBytes // similar to memcmp
case copyBytes // similar to memmove
case getScreenAddress // app specific
}
I tried to make the sysOp's dependency minimal, for example having <
is enough, on top on that in the library I can express other things like >, <=, etc.
And that's pretty much it. The library is not ready yet to be shown but here's how the app itself look (the one that shows the above image) with this setup:
// -parse-stdlib in flags and no `import Swift` here
private var screenBaseAddress = Pointer<UInt8>()
private let stride = Int(.x50) // assuming 640 pixel wide B&W screen
private let colCount = Int(.x50) // assuming 640 pixel wide B&W screen and 8x8 letters
private let rowCount = Int(.x19) // assuming 8x8 letters
typealias Letter = Tuple8<UInt8> // 8 rows of 8 pixels
// ascii chars are less than 128
var letters: Array128<Letter> = .init(count: Int(.x80), tuple: .init(repeating: .init()))
func initLetter() {
// I am lazy to populate the whole table, these few will do for "Hello, World!"
letters.setValue(.init(.x00, .x00, .x00, .x00, .x00, .x00, .x00, .x00), at: Int(.x20)) // space
letters.setValue(.init(.x08, .x08, .x08, .x08, .x00, .x00, .x08, .x00), at: Int(.x21)) // !
letters.setValue(.init(.x00, .x00, .x00, .x00, .x18, .x08, .x10, .x00), at: Int(.x2C)) // ,
letters.setValue(.init(.x22, .x22, .x22, .x3E, .x22, .x22, .x22, .x00), at: Int(.x48)) // H
letters.setValue(.init(.x22, .x22, .x22, .x2A, .x2A, .x2A, .x14, .x00), at: Int(.x57)) // W
letters.setValue(.init(.x02, .x02, .x1A, .x26, .x22, .x22, .x16, .x00), at: Int(.x64)) // d
letters.setValue(.init(.x00, .x00, .x1C, .x22, .x3E, .x20, .x1C, .x00), at: Int(.x65)) // e
letters.setValue(.init(.x18, .x08, .x08, .x08, .x08, .x08, .x1C, .x00), at: Int(.x6C)) // l
letters.setValue(.init(.x00, .x00, .x1C, .x22, .x22, .x22, .x1C, .x00), at: Int(.x6F)) // o
letters.setValue(.init(.x00, .x00, .x2C, .x32, .x20, .x20, .x20, .x00), at: Int(.x72)) // r
}
func drawLetter(_ letterIndex: UInt8, column: Int, row: Int) {
let letterIndex = Int(letterIndex)
guard case .true = column >= Int() && column < colCount && row >= Int() && row <= rowCount - Int(.x08) else {
return
}
let letter = letters[letterIndex]
var i = Int()
while case .true = i < Int(.x08) {
let byte = letter[i]
i += Int(.x01)
(screenBaseAddress + (row * Int(.x05) + i) * stride + column).pointee = byte
}
}
func drawString(_ string: Array16<UInt8>, column: Int = Int(), row: Int = Int()) {
var column = column
var row = row
var i = Int()
let count = string.count
while case .true = i < count {
let letterIndex = string[i]
i += Int(.x01)
drawLetter(letterIndex, column: column, row: row)
column += Int(.x01)
if case .true = column >= colCount {
column = Int()
row += Int(.x01)
if case .true = row >= rowCount {
row = Int()
}
}
}
}
func main() {
initScreen()
initLetter()
drawString(
.init(.x48, .x65, .x6C, .x6C, .x6F, .x2C, .x20, .x57, .x6F, .x72, .x6C, .x64, .x21),
column: Int(.x21), row: Int(.x0C))
}
Going forward would like to do a few things:
- learn how to use integer literals in this environment.
Int(.x42)
or even .x42
can't beat 66
- learn how to use:
while i < count {
...
if column >= colCount {
instead of currently awkward:
while case .true = i < count {
...
if case .true = column >= colCount {
- later on do float literals and string literals
- fix obvious things (e.g. make Int signed, etc)
- For some reason trying to use "set" in subscripts leads to the following compiler crash in this minimal environment:
1. Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
2. Compiling with the current language version
3. While evaluating request ASTLoweringRequest(Lowering AST to SIL for module MyModuleName)
4. While emitting property descriptor for 'subscript(_:)' (at /Users/tera/TestApp/main.swift:273:5)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0 swift-frontend 0x00000001075abe70 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1 swift-frontend 0x00000001075aae74 llvm::sys::RunSignalHandlers() + 112
2 swift-frontend 0x00000001075ac4f4 SignalHandler(int) + 344
3 libsystem_platform.dylib 0x00000001b290f4a4 _sigtramp + 56
4 swift-frontend 0x0000000103223838 getOrCreateKeyPathGetter(swift::Lowering::SILGenModule&, swift::AbstractStorageDecl*, swift::SubstitutionMap, swift::GenericEnvironment*, swift::ResilienceExpansion, llvm::ArrayRef<std::__1::pair<swift::CanType, swift::SILType> >, swift::CanType, swift::CanType) + 688
5 swift-frontend 0x0000000103220434 swift::Lowering::SILGenModule::emitKeyPathComponentForDecl(swift::SILLocation, swift::GenericEnvironment*, swift::ResilienceExpansion, unsigned int&, bool&, swift::SubstitutionMap, swift::AbstractStorageDecl*, llvm::ArrayRef<swift::ProtocolConformanceRef>, swift::CanType, swift::DeclContext*, bool) + 2624
6 swift-frontend 0x00000001031b7e30 swift::Lowering::SILGenModule::tryEmitPropertyDescriptor(swift::AbstractStorageDecl*) + 1020
7 swift-frontend 0x00000001032976b0 swift::ASTVisitor<SILGenExtension, void, void, void, void, void, void>::visit(swift::Decl*) + 824
8 swift-frontend 0x0000000103293ad8 SILGenExtension::emitExtension(swift::ExtensionDecl*) + 164
9 swift-frontend 0x00000001031bad14 swift::ASTVisitor<swift::Lowering::SILGenModule, void, void, void, void, void, void>::visit(swift::Decl*) + 1176
10 swift-frontend 0x00000001031b90e8 swift::ASTLoweringRequest::evaluate(swift::Evaluator&, swift::ASTLoweringDescriptor) const + 3356
11 swift-frontend 0x000000010328559c swift::SimpleRequest<swift::ASTLoweringRequest, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> > (swift::ASTLoweringDescriptor), (swift::RequestFlags)9>::evaluateRequest(swift::ASTLoweringRequest const&, swift::Evaluator&) + 216
12 swift-frontend 0x00000001031bc8e8 llvm::Expected<swift::ASTLoweringRequest::OutputType> swift::Evaluator::getResultUncached<swift::ASTLoweringRequest>(swift::ASTLoweringRequest const&) + 628
13 swift-frontend 0x0000000102bff654 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 7208
14 swift-frontend 0x0000000102b9fc44 swift::mainEntry(int, char const**) + 3940
15 dyld 0x0000000109ff908c start + 520
Command SwiftEmitModule failed with a nonzero exit code
so for now I'm only using subscripts with getters and instead of setters using some explicit setValue()
functions.
Thanks for reading, hope this would be somehow useful to those using swift apps on embedded systems or doing "custom standard library" for other reasons, and would be glad for your feedback and suggestions.
Edit:
Perhaps possible to do most things, but I currently still see no way to be able reading from or writing to an externally provided memory address location with the "pure" approach. Remember there are no things like "unsafeBitCast" or Unsafe[Mutable][Raw][Buffer]Pointer
available. I can read/write the content of my own "pure" vars and tuples, etc, although not to, say, 0xC0000000 memory address location which was provided externally, and perhaps dynamically. I don't see Swift provides this ability at all (cp. with Modula-2, where it is part of the syntax, albeit in a limited way (you can specify an associated constant address for a variable)). With the "minimalist" swift (no standard library, no built-ins, no import "Swift", no C functions available to call, "the vacuum of outer space" kind of environment – I would not even know where to start if I were to, say, read a byte from 0xC0000000 location – (something as trivial as char v = *(char*)0xC0000000)
in C) – (and in practice I'd somehow need to know that location to begin with!)