@@ -30,62 +30,131 @@ func checkMac(macSecret: secret(string), message: string, mac: string) -> bool {
30
30
return mac == computedMac
31
31
}
32
32
33
- func computeMac(macSecret: secret( string) , message:string) -> string {
33
+ func computeMac(macSecret: string, message:string) -> string {
34
34
// A popular MAC algorithm.
35
35
return hmacSha256(macSecret, message)
36
36
}
37
37
```
38
38
39
39
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.
45
56
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
+ }
51
66
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
+ }
54
80
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)
55
94
```
56
- class Node(self) {
57
- }
58
95
59
- relation OneToOne Node:"ParentLeft" Node:"Left" cascade
60
- relation OneToOne Node:"ParentRight" Node:"Right" cascade
96
+ When run, this prints:
61
97
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 = [""]
72
140
```
73
141
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.
80
149
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
83
152
Games] ( https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html ) .
84
153
(Rune is not yet multi-threaded). The only close competitor is C++, where the
85
154
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
89
158
benchmarks.
90
159
91
160
For more information about Rune, see additional documentation in
0 commit comments