@richardwhaling
Nov 14 2019
"the domain of programs that demand a mental model of the computer as a machine"
It doesn't have to be this way.
Systems programming can be elegant, fun, and done in a language you enjoy.
My hot take:
from learning C, I acquired an intuitive understanding of how to solve problems in an abstract von Neumann machine
(1903-1957)
First Draft of a Report on the EDVAC
(1945)
Electronic Discrete Variable Automatic Computer
EDVAC was the first stored-program computer, which stored data and code in byte-addressable memory.
Earlier computers like ENIAC and Colossus were programmed by patch cables and switches, which was theoretically Turing-complete, but impractical to program.
Theoretical description of a realized Universal Turing Machine, i.e., a general-purpose computer
Unlike a Universal Turing Machines, Von Neumann machines were practical to construct and program
In 7 years, the first computer scientists invented:
An explosion of applications and discoveries enabled by a comprehensible, practical model of a programmable general-purpose computer
C presents an enduring abstract model
of a random-access stored-program computer, with:
Hot Take:
these are the fundamental techniques
of programming a Von Neumann machine
Scala Native is a scalac compiler plugin that compiles Scala programs to binary executables ahead-of-time
Noteworthy for: its advanced optimizer, lightweight runtime, advanced GC, and C interop
Not a JVM - Graal compiles JVM bytecode to machine binary, very different model
Because it understands Scala, Scala Native can provide an elegant DSL for low-level programming
with all the capabilities of C
We're going to illustrate the fundamental techniques:
Each with a short program of less than 20 lines of code
Caveat:
Regular Scala works just fine in Scala Native.
All the features you'll see here belong to the scalanative.unsafe API
The slides that follow will contain extremely unindiomatic, imperative Scala
val i:Int = 6
println(s"Int i has value ${i} and size ${sizeof[Int]} bytes")
val b:Byte = 4
println(s"Byte b has value ${b} and size ${sizeof[Byte]} bytes")
val d:Double = 1.0
println(s"Double d has value ${d} and size ${sizeof[Double]} bytes")
val jPtr:Ptr[Int] = stackalloc[Int]
println(s"jPtr has value ${jPtr} and size ${sizeof[Ptr[Int]]} bytes")
val j:Int = !jPtr
println(s"j has value ${j} and size ${sizeof[Int]}")
!jPtr = 5
println(s"jPtr has value ${jPtr} and size ${sizeof[Ptr[Int]]} bytes")
val j2:Int = !jPtr
println(s"j2 has value ${j2} and size ${sizeof[Int]}, j has value ${j}")
val arraySize = 16 * sizeof[Int]
val allocation:Ptr[Byte] = stdlib.malloc(arraySize)
val intArray = allocation.asInstanceOf[Ptr[Int]]
for (i <- 0 to 16) {
intArray(i) = i * 2
}
for (i <- 0 to 16) {
val address = intArray + i
val item = intArray(i)
val check = !(intArray + i) == intArray(i)
println(s"item $i at address ${intArray + i} has value $item, check: $check")
}
// just to be safe
stdlib.free(allocation)
val hello:CString = c"hello, world"
val helloLen = string.strlen(hello)
val helloString:String = fromCString(hello)
println(s"the string ${helloString} at ${hello} is ${helloLen} bytes long")
println(s"the CString value 'str' is ${sizeof[CString]} bytes long")
for (offset <- 0L to helloLen) {
val chr:CChar = hello(offset)
println(s"${chr.toChar} (${chr}) at ${hello + offset} is ${sizeof[CChar]} bytes long")
}
+--------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| Offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D |
+--------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| Char | H | e | l | l | o | , | | w | o | r | l | d | ! | |
| Hex | 48 | 65 | 6C | 6C | 6F | 2C | 20 | 77 | 6F | 72 | 6C | 64 | 21 | 00 |
+--------+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
type LabeledPoint = CStruct3[CString,Int,Int]
val point:Ptr[LabeledPoint] = stackalloc[LabeledPoint]
point._1 = c"foo"
point._2 = 3
point._3 = 5
println(s"struct field ${point.at1} has value ${point._1}")
println(s"struct field ${point.at2} has value ${point._2}")
println(s"struct field ${point.at3} has value ${point._3}")
println(s"struct ${point} has size ${sizeof[LabeledPoint]}")
+--------+----+----+----+----+----+----+----+----+
| Offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+--------+----+----+----+----+----+----+----+----+
| Value | 5 | 12 |
+--------+----+----+----+----+----+----+----+----+
| Hex | 05 | 00 | 00 | 00 | 0C | 00 | 00 | 00 |
+--------+----+----+----+----+----+----+----+----+
type LabeledFoo = CStruct2[Int,Int]
type LabeledBar = CStruct2[Int,Long]
val FOO = 0
val BAR = 1
println(s"LabeledFoo size is ${sizeof[LabeledFoo]}")
println(s"LabeledBar size is ${sizeof[LabeledBar]}")
val array = stdlib.malloc(8 * sizeof[LabeledBar]).asInstanceOf[Ptr[LabeledBar]]
for (i <- 0 until 8) {
array(i)._2 = 0
if (i % 2 == 0) {
val item = array(i).asInstanceOf[LabeledFoo]
item._1 = FOO
item._2 = Random.nextInt() % 16
} else {
val item = array(i).asInstanceOf[LabeledBar]
item._1 = BAR
item._2 = Random.nextLong % 64
}
}
for (j <- 0 until 8) {
val tag = array(j)._1
if (tag == FOO) {
val item = array(j).asInstanceOf[LabeledFoo]
println(s"Foo: ${tag} at $j = ${item._2}")
} else {
val item = array(j).asInstanceOf[LabeledBar]
println(s"Bar: ${tag} at $j = ${item._2}")
}
}
+--------+----+----+----+----+----+----+----+----+
| Offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+--------+----+----+----+----+----+----+----+----+
| Value | 5 | 12 |
+--------+----+----+----+----+----+----+----+----+
| Hex | 05 | 00 | 00 | 00 | 0C | 00 | 00 | 00 |
+--------+----+----+----+----+----+----+----+----+
+--------+----+----+----+----+----+----+----+----+----+----+----+----+
| Offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
+--------+----+----+----+----+----+----+----+----+----+----+----+----+
| Value | 3 | 29 |
+--------+----+----+----+----+----+----+----+----+----+----+----+----+
| Hex | 03 | 00 | 00 | 00 | 1D | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
+--------+----+----+----+----+----+----+----+----+----+----+----+----+
type Comparator = CFuncPtr2[Ptr[Byte],Ptr[Byte],Int]
type Record = CStruct2[Int,Int]
val comp = new Comparator {
def apply(aPtr:Ptr[Byte], bPtr:Ptr[Byte]):Int = {
val a = !(aPtr.asInstanceOf[Ptr[Record]])
val b = !(bPtr.asInstanceOf[Ptr[Record]])
a._2 - b._2
}
}
val size = 8
val recordArray:Ptr[Record] = stdlib.malloc(8 * sizeof[Record]).asInstanceOf[Ptr[Record]]
for (i <- 0 until 8) {
recordArray(i)._1 = i
recordArray(i)._2 = Random.nextInt() % 256
}
stdlib.qsort(recordArray.asInstanceOf[Ptr[Byte]],8,sizeof[Record],comp)
for (i <- 0 until 8) {
val rec = recordArray(i)
println(s"${i}: random value ${rec._2} from original position ${rec._1}")
}