Skip to content

Commit ca50470

Browse files
committed
Updated README.md to use Human example.
1 parent 1cb1b90 commit ca50470

File tree

3 files changed

+165
-38
lines changed

3 files changed

+165
-38
lines changed

README.md

+107-38
Original file line numberDiff line numberDiff line change
@@ -30,62 +30,131 @@ func checkMac(macSecret: secret(string), message: string, mac: string) -> bool {
3030
return mac == computedMac
3131
}
3232
33-
func computeMac(macSecret: secret(string), message:string) -> string {
33+
func computeMac(macSecret: string, message:string) -> string {
3434
// A popular MAC algorithm.
3535
return hmacSha256(macSecret, message)
3636
}
3737
```
3838

3939
Can you see the potential security flaw? Suppose the attacker can tell how long
40-
it takes for the comparison `mac == computedMac` to run. If the first byte of
41-
an attacker-chosen `mac` is wrong for the attacker-chosen `message`, the
42-
loop terminates after just one comparison. With 256 attempts, the attacker can
43-
find the first byte of the expected MAC for the attacker-controlled `message`.
44-
Repeating this process, the attacker can forge the entire MAC.
40+
it takes for the `mac == computedMac` to run. If the first byte of an
41+
attacker-chosen `mac` is wrong for the attacker-chosen `message`, the loop
42+
terminates after just one comparison. With 256 attempts, the attacker can find
43+
the first byte of the expected MAC for the attacker-controlled `message`.
44+
Repeating this process, the attacker can forge an entire MAC.
45+
46+
Users of Rune are protected, because the compiler sees that `macSecret` is
47+
secret, and thus the result of `hmacSha256` is secret. The string comparison
48+
operator, when either operand is secret, will run in constant time, revealing no
49+
timing information to the attacker. Care must still be taken in Rune, but many
50+
common mistakes like this are detected by the compiler, and either fixed or
51+
flagged as an error.
52+
53+
As for the speed and safety of Rune's memory management, consider a simple
54+
`Human` class. This can be tricky to model in some languages, yet is trivial in
55+
both SQL and Rune.
4556

46-
Rune is not affected, because it sees that `macSecret` is secret, and thus the
47-
result of `hmacSha256` is secret. The string comparison operator when either
48-
operand is secret will be executed in constant time, revealing no timing
49-
information to the attacker. Care must still be taken in Rune, but many common
50-
mistakes like this are detected by the compiler.
57+
```
58+
class Human(self, name: string, mother: Human = null, father: Human = null) {
59+
self.name = name
60+
if !isnull(mother) {
61+
mother.appendMotheredHuman(self)
62+
}
63+
if !isnull(father) {
64+
father.appendFatheredHuman(self)
65+
}
5166
52-
As for the speed of Rune's memory management, the `binarytree.rn` benchmark begins
53-
to show what is possible. In simplified form:
67+
func printFamilyTree(self, level: u32) {
68+
for i in range(level) {
69+
print " "
70+
}
71+
println self.name
72+
for child in self.motheredHumans() {
73+
child.printFamilyTree(level + 1)
74+
}
75+
for child in self.fatheredHumans() {
76+
child.printFamilyTree(level + 1)
77+
}
78+
}
79+
}
5480
81+
relation DoublyLinked Human:"Mother" Human:"Mothered" cascade
82+
relation DoublyLinked Human:"Fater" Human:"Fathered" cascade
83+
84+
adam = Human("Adam")
85+
eve = Human("Eve")
86+
cain = Human("Cain", eve, adam)
87+
abel = Human("Abel", eve, adam)
88+
alice = Human("Alice", eve, adam)
89+
bob = Human ("Bob", eve, adam)
90+
malory = Human("Malory", alice, abel)
91+
abel.destroy()
92+
adam.printFamilyTree(0u32)
93+
eve.printFamilyTree(0u32)
5594
```
56-
class Node(self) {
57-
}
5895

59-
relation OneToOne Node:"ParentLeft" Node:"Left" cascade
60-
relation OneToOne Node:"ParentRight" Node:"Right" cascade
96+
When run, this prints:
6197

62-
func makeTree(depth: Uint) -> Node {
63-
node = Node()
64-
if depth != 0 {
65-
left = makeTree(depth - 1)
66-
right = makeTree(depth - 1)
67-
node.insertLeftNode(left)
68-
node.insertRightNode(right)
69-
}
70-
return node
71-
}
98+
```
99+
Adam
100+
Cain
101+
Alice
102+
Bob
103+
Eve
104+
Cain
105+
Alice
106+
Bob
107+
```
108+
109+
Note that Abel and Malory are not listed. This is because we didn't just kill
110+
Abel, we destroyed Abel, and this caused all of Abel's children to be
111+
recursively destroyed.
112+
113+
Relation statements are similar to columns in SQL tables. A table with a Mother
114+
and Father column has two many-to-one relations in a database.
115+
116+
Relation statements give the Rune compiler critical hints for memory
117+
optimization. Objects which the compiler can prove are always in cascade-delete
118+
relationships do not need to be reference counted. The relation statements also
119+
inform the compiler to update Node's destructor to recursively destroy children.
120+
**Rune programmers never write destructors**, removing this footgun from the
121+
language.
122+
123+
To understand why Rune's generated SoA code is so efficient, consider the arrays
124+
of properties created for the Human example above:
125+
126+
127+
```
128+
nextFree = [0u32]
129+
motherHuman = [null(human.Human(string, null, null))]
130+
prevHumanMotheredHuman = [null(human.Human(string, null, null))]
131+
nextHumanMotheredHuman = [null(human.Human(string, null, null))]
132+
firstMotheredHuman = [null(human.Human(string, null, null))]
133+
lastMotheredHuman = [null(human.Human(string, null, null))]
134+
faterHuman = [null(human.Human(string, null, null))]
135+
prevHumanFatheredHuman = [null(human.Human(string, null, null))]
136+
nextHumanFatheredHuman = [null(human.Human(string, null, null))]
137+
firstFatheredHuman = [null(human.Human(string, null, null))]
138+
lastFatheredHuman = [null(human.Human(string, null, null))]
139+
name = [""]
72140
```
73141

74-
The `relation` statements give the Rune compiler critical hints for memory
75-
optmization. It figures out not to reference count objects already in a
76-
cascade-delete relationship (all Nodes but the root). It also auto-generates a
77-
safe destructor: Rune programmers never write destructors, removing this footgun
78-
from the language. Consider what happens in C++ if we call delete on a child
79-
node, without manually maintaining up back-pointers to parent nodes?
142+
A total of 12 arrays are allocated for the Human class in SoA memory layout. In
143+
`printFamilyTree`, we only access 5 of them. In AoS memory layout, all 12
144+
fields would be loaded into cache during the tree traversal, and all fields
145+
would be 64 bits on a 64-bit machine. In Rune, only the string references are
146+
64-bits by default. As a result, **Rune loads only 25% as much data into
147+
cache** during the traversal, improving memory load times, while simultaneously
148+
improving cache hit rates.
80149

81-
This code already runs faster than any other single-threaded result in the
82-
[Benchmark
150+
This is why Rune's binarytree.rn code already runs faster than any other
151+
single-threaded result in the [Benchmark
83152
Games](https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html).
84153
(Rune is not yet multi-threaded). The only close competitor is C++, where the
85154
author uses the little-known `MemoryPool` class from the `<memory>` library.
86-
Not only is Rune's SoA memory layout faster, due to improved cache performance,
87-
its solution is generic: we can create/destroy Node objects arbitrarily, unlike
88-
the C++ benchmark. When completed, we expect Rune to win most memory-intensive
155+
Not only is Rune's SoA memory layout faster, but its solution is more generic:
156+
we can create/destroy Node objects arbitrarily, unlike the C++ benchmark based
157+
on `MemoryPool`. When completed, we expect Rune to win most memory-intensive
89158
benchmarks.
90159

91160
For more information about Rune, see additional documentation in

tests/human.rn

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2021 Google LLC.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
class Human(self, name: string, mother: Human = null, father: Human = null) {
16+
self.name = name
17+
if !isnull(mother) {
18+
mother.appendMotheredHuman(self)
19+
}
20+
if !isnull(father) {
21+
father.appendFatheredHuman(self)
22+
}
23+
24+
func printFamilyTree(self, level: u32) {
25+
for i in range(level) {
26+
print " "
27+
}
28+
println self.name
29+
for child in self.motheredHumans() {
30+
child.printFamilyTree(level + 1)
31+
}
32+
for child in self.fatheredHumans() {
33+
child.printFamilyTree(level + 1)
34+
}
35+
}
36+
}
37+
38+
relation DoublyLinked Human:"Mother" Human:"Mothered" cascade
39+
relation DoublyLinked Human:"Fater" Human:"Fathered" cascade
40+
41+
adam = Human("Adam")
42+
eve = Human("Eve")
43+
cain = Human("Cain", eve, adam)
44+
abel = Human("Abel", eve, adam)
45+
alice = Human("Alice", eve, adam)
46+
bob = Human ("Bob", eve, adam)
47+
malory = Human("Malory", alice, abel)
48+
abel.destroy()
49+
adam.printFamilyTree(0u32)
50+
eve.printFamilyTree(0u32)

tests/human.stdout

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Adam
2+
Cain
3+
Alice
4+
Bob
5+
Eve
6+
Cain
7+
Alice
8+
Bob

0 commit comments

Comments
 (0)