浏览代码

Are We Fast Yet

Hugo Musso Gualandi 3 年之前
父节点
当前提交
17115153bd
共有 10 个文件被更改,包括 10135 次插入0 次删除
  1. 697 0
      experiments/bounce.lua
  2. 1532 0
      experiments/cd.lua
  3. 1251 0
      experiments/havlak.lua
  4. 1196 0
      experiments/json.lua
  5. 1200 0
      experiments/json_jit.lua
  6. 688 0
      experiments/list.lua
  7. 658 0
      experiments/permute.lua
  8. 1115 0
      experiments/richards.lua
  9. 1115 0
      experiments/richards_jit.lua
  10. 683 0
      experiments/towers.lua

+ 697 - 0
experiments/bounce.lua

@@ -0,0 +1,697 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local Ball = {_CLASS = 'Ball'} do
+
+local abs = math.abs
+
+function Ball.new (random)
+    local obj = {
+        x = random:next() % 500,
+        y = random:next() % 500,
+        x_vel = (random:next() % 300) - 150,
+        y_vel = (random:next() % 300) - 150,
+    }
+    return setmetatable(obj, {__index = Ball})
+end
+
+function Ball:bounce ()
+    local x_limit, y_limit = 500, 500
+    local bounced = false
+    self.x = self.x + self.x_vel
+    self.y = self.y + self.y_vel
+    if self.x > x_limit then
+        self.x = x_limit
+        self.x_vel = 0 - abs(self.x_vel)
+        bounced = true
+    end
+    if self.x < 0 then
+        self.x = 0
+        self.x_vel = abs(self.x_vel)
+        bounced = true
+    end
+    if self.y > y_limit then
+        self.y = y_limit
+        self.y_vel = 0 - abs(self.y_vel)
+        bounced = true
+    end
+    if self.y < 0 then
+        self.y = 0
+        self.y_vel = abs(self.y_vel)
+        bounced = true
+    end
+    return bounced
+end
+
+end -- class Ball
+
+local bounce = {} do
+setmetatable(bounce, {__index = benchmark})
+
+function bounce:benchmark ()
+    local random     = Random.new()
+    local ball_count = 100
+    local bounces    = 0
+    local balls      = {}
+
+    for i = 1, ball_count do
+        balls[i] = Ball.new(random)
+    end
+
+    for _ = 1, 50 do
+        for i = 1, #balls do
+            local ball = balls[i]
+            if ball:bounce() then
+                bounces = bounces + 1
+            end
+        end
+    end
+    return bounces
+end
+
+function bounce:verify_result (result)
+    return 1331 == result
+end
+
+end -- object bounce
+
+return function(N)
+    bounce:inner_benchmark_loop(N)
+end

+ 1532 - 0
experiments/cd.lua

@@ -0,0 +1,1532 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local MIN_X = 0.0
+local MIN_Y = 0.0
+local MAX_X = 1000.0
+local MAX_Y = 1000.0
+local MIN_Z = 0.0
+local MAX_Z = 10.0
+local PROXIMITY_RADIUS = 1.0
+local GOOD_VOXEL_SIZE  = PROXIMITY_RADIUS * 2.0
+
+local Vector2D = {_CLASS = 'Vector2D'} do
+
+function Vector2D.new (x, y)
+    local obj = {x = x, y = y}
+    return setmetatable(obj, {__index = Vector2D})
+end
+
+function Vector2D:plus (other)
+    return Vector2D.new(self.x + other.x, self.y + other.y)
+end
+
+function Vector2D:minus (other)
+    return Vector2D.new(self.x - other.x, self.y - other.y)
+end
+
+local function compare_numbers (a, b)
+    if     a == b then
+        return 0
+    elseif a < b then
+        return -1
+    elseif a > b then
+        return 1
+    -- We say that NaN is smaller than non-NaN.
+    elseif a == a then
+        return 1
+    else
+        return -1
+    end
+end
+
+function Vector2D:compare_to (other)
+    local result = compare_numbers(self.x, other.x)
+    if result ~= 0 then
+        return result
+    else
+        return compare_numbers(self.y, other.y)
+    end
+end
+
+end -- class Vector2D
+
+local Vector3D = {_CLASS = 'Vector3D'} do
+
+local sqrt = math.sqrt
+
+function Vector3D.new (x, y, z)
+    local obj = {x = x, y = y, z = z}
+    return setmetatable(obj, {__index = Vector3D})
+end
+
+function Vector3D:plus (other)
+    return Vector3D.new(self.x + other.x, self.y + other.y, self.z + other.z)
+end
+
+function Vector3D:minus (other)
+    return Vector3D.new(self.x - other.x, self.y - other.y, self.z - other.z)
+end
+
+function Vector3D:dot (other)
+    return self.x * other.x + self.y * other.y + self.z * other.z
+end
+
+function Vector3D:squared_magnitude ()
+    return self:dot(self)
+end
+
+function Vector3D:magnitude ()
+    return sqrt(self:squared_magnitude())
+end
+
+function Vector3D:times (amount)
+    return Vector3D.new(self.x * amount, self.y * amount, self.z * amount)
+end
+
+end -- class Vector3D
+
+local function tree_minimum (x)
+    local current = x
+    while current.left do
+        current = current.left
+    end
+    return current
+end
+
+local Node = {_CLASS = 'Node'} do
+
+function Node.new (key, value)
+    local obj = {
+        key    = key,
+        value  = value,
+        left   = nil,
+        right  = nil,
+        parent = nil,
+        color  = 'red',
+    }
+    return setmetatable(obj, {__index = Node})
+end
+
+function Node:successor ()
+    local x = self
+    if x.right then
+        return tree_minimum(x.right)
+    end
+
+    local y = x.parent
+    while y and x == y.right do
+        x = y
+        y = y.parent
+    end
+    return y
+end
+
+end -- class Node
+
+local RbtEntry = {_CLASS = 'RbtEntry'} do
+
+function RbtEntry.new(key, value)
+    local obj = {
+        key   = key,
+        value = value
+    }
+    return setmetatable(obj, {__index = RbtEntry})
+end
+
+end -- class RbtEntry
+
+local InsertResult = {_CLASS = 'InsertResult'} do
+
+function InsertResult.new(is_new_entry, new_node, old_value)
+    local obj = {
+        is_new_entry = is_new_entry,
+        new_node     = new_node,
+        old_value    = old_value,
+    }
+    return setmetatable(obj, {__index = InsertResult})
+end
+
+end -- class InsertResult
+
+local RedBlackTree = {_CLASS = 'RedBlackTree'} do
+
+function RedBlackTree.new ()
+    local obj = {root = nil}
+    return setmetatable(obj, {__index = RedBlackTree})
+end
+
+function RedBlackTree:put (key, value)
+    local insertion_result = self:tree_insert(key, value)
+    if not insertion_result.is_new_entry then
+        return insertion_result.old_value
+    end
+
+    local x = insertion_result.new_node
+
+    while x ~= self.root and x.parent.color == 'red' do
+        if x.parent == x.parent.parent.left then
+            local y = x.parent.parent.right
+            if y and y.color == 'red' then
+                -- Case 1
+                x.parent.color = 'black'
+                y.color = 'black'
+                x.parent.parent.color = 'red'
+                x = x.parent.parent
+            else
+                if x == x.parent.right then
+                    -- Case 2
+                    x = x.parent
+                    self:left_rotate(x)
+                end
+
+                -- Case 3
+                x.parent.color = 'black'
+                x.parent.parent.color = 'red'
+                self:right_rotate(x.parent.parent)
+            end
+        else
+            -- Same as "then" clause with "right" and "left" exchanged.
+            local y = x.parent.parent.left
+            if y and y.color == 'red' then
+                -- Case 1
+                x.parent.color = 'black'
+                y.color = 'black'
+                x.parent.parent.color = 'red'
+                x = x.parent.parent
+            else
+                if x == x.parent.left then
+                    -- Case 2
+                    x = x.parent
+                    self:right_rotate(x)
+                end
+
+                -- Case 3
+                x.parent.color = 'black'
+                x.parent.parent.color = 'red'
+                self:left_rotate(x.parent.parent)
+            end
+        end
+    end
+
+    self.root.color = 'black'
+    return nil
+end
+
+function RedBlackTree:remove (key)
+    local z = self:find_node(key)
+    if not z then
+        return nil
+    end
+
+    -- Y is the node to be unlinked from the tree.
+    local y
+    if not z.left or not z.right then
+        y = z
+    else
+        y = z:successor()
+    end
+
+    -- Y is guaranteed to be non-null at this point.
+    local x
+    if y.left then
+        x = y.left
+    else
+        x = y.right
+    end
+
+    -- X is the child of y which might potentially replace y
+    -- in the tree. X might be null at this point.
+    local x_parent
+    if x then
+        x.parent = y.parent
+        x_parent = x.parent
+    else
+        x_parent = y.parent
+    end
+
+    if not y.parent then
+        self.root = x
+    else
+        if y == y.parent.left then
+            y.parent.left = x
+        else
+            y.parent.right = x
+        end
+    end
+
+    if y ~= z then
+        if y.color == 'black' then
+            self:remove_fixup(x, x_parent)
+        end
+
+        y.parent = z.parent
+        y.color  = z.color
+        y.left   = z.left
+        y.right  = z.right
+
+        if z.left then
+            z.left.parent = y
+        end
+        if z.right then
+            z.right.parent = y
+        end
+        if z.parent then
+            if z.parent.left == z then
+                z.parent.left = y
+            else
+                z.parent.right = y
+            end
+        else
+            self.root = y
+        end
+    elseif y.color == 'black' then
+        self:remove_fixup(x, x_parent)
+    end
+
+    return z.value
+end
+
+function RedBlackTree:get (key)
+    local node = self:find_node(key)
+    if node then
+        return node.value
+    end
+    return nil
+end
+
+function RedBlackTree:for_each (fn)
+    if not self.root then
+        return
+    end
+    local current = tree_minimum(self.root)
+    while current do
+        fn(RbtEntry.new(current.key, current.value))
+        current = current:successor()
+     end
+end
+
+function RedBlackTree:find_node (key)
+    local current = self.root
+    while current do
+        local comparison_result = key:compare_to(current.key)
+        if     comparison_result == 0 then
+            return current
+        elseif comparison_result < 0 then
+            current = current.left
+        else
+            current = current.right
+        end
+    end
+    return nil
+end
+
+function RedBlackTree:tree_insert (key, value)
+    local y = nil
+    local x = self.root
+
+    while x do
+        y = x
+        local comparison_result = key:compare_to(x.key)
+        if comparison_result < 0 then
+            x = x.left
+        elseif comparison_result > 0 then
+            x = x.right
+        else
+            local old_value = x.value
+            x.value = value
+            return InsertResult.new(false, nil, old_value)
+        end
+    end
+
+    local z = Node.new(key, value)
+    z.parent = y
+
+    if not y then
+        self.root = z
+    else
+        if key:compare_to(y.key) < 0 then
+            y.left = z
+        else
+            y.right = z
+        end
+    end
+    return InsertResult.new(true, z, nil)
+end
+
+function RedBlackTree:left_rotate (x)
+    local y = x.right
+
+    -- Turn y's left subtree into x's right subtree.
+    x.right = y.left
+    if y.left then
+        y.left.parent = x
+    end
+
+    -- Link x's parent to y.
+    y.parent = x.parent
+    if not x.parent then
+        self.root = y
+    else
+        if x == x.parent.left then
+            x.parent.left = y
+        else
+            x.parent.right = y
+        end
+    end
+
+    -- Put x on y's left.
+    y.left   = x
+    x.parent = y
+
+    return y
+end
+
+function RedBlackTree:right_rotate (y)
+    local x = y.left
+
+    -- Turn x's right subtree into y's left subtree.
+    y.left = x.right
+    if x.right then
+        x.right.parent = y
+    end
+
+    -- Link y's parent to x.
+    x.parent = y.parent
+    if not y.parent then
+        self.root = x
+    else
+        if y == y.parent.left then
+            y.parent.left = x
+        else
+            y.parent.right = x
+        end
+    end
+
+    x.right = y
+    y.parent = x
+
+    return x
+end
+
+function RedBlackTree:remove_fixup (x, x_parent)
+    while x ~= self.root and (not x or x.color == 'black') do
+        if x == x_parent.left then
+            -- Note: the text points out that w cannot be null.
+            -- The reason is not obvious from simply looking at the code;
+            -- it comes about from the properties of the red-black tree.
+            local w = x_parent.right
+            if w.color == 'red' then
+                 -- Case 1
+                w.color = 'black'
+                x_parent.color = 'red'
+                self:left_rotate(x_parent)
+                w = x_parent.right
+            end
+            if (not w.left or w.left.color == 'black') and
+               (not w.right or w.right.color == 'black') then
+                -- Case 2
+                w.color = 'red'
+                x = x_parent
+                x_parent = x.parent
+            else
+                if not w.right or w.right.color == 'black' then
+                    -- Case 3
+                    w.left.color = 'black'
+                    w.color = 'red'
+                    self:right_rotate(w)
+                    w = x_parent.right
+                end
+                -- Case 4
+                w.color = x_parent.color
+                x_parent.color = 'black'
+                if w.right then
+                    w.right.color = 'black'
+                end
+                self:left_rotate(x_parent)
+                x = self.root
+                x_parent = x.parent
+            end
+        else
+            -- Same as "then" clause with "right" and "left" exchanged.
+            local w = x_parent.left
+            if w.color == 'red' then
+                -- Case 1
+                w.color = 'black'
+                x_parent.color = 'red'
+                self:right_rotate(x_parent)
+                w = x_parent.left
+            end
+            if (not w.right or w.right.color == 'black') and
+               (not w.left or w.left.color == 'black') then
+                -- Case 2
+                w.color = 'red'
+                x = x_parent
+                x_parent = x.parent
+            else
+                if not w.left or w.left.color == 'black' then
+                    -- Case 3
+                    w.right.color = 'black'
+                    w.color = 'red'
+                    self:left_rotate(w)
+                    w = x_parent.left
+                end
+                -- Case 4
+                w.color = x_parent.color
+                x_parent.color = 'black'
+                if w.left then
+                    w.left.color = 'black'
+                end
+                self:right_rotate(x_parent)
+                x = self.root
+                x_parent = x.parent
+            end
+        end
+    end
+    if x then
+        x.color = 'black'
+    end
+end
+
+end -- class RedBlackTree
+
+local CallSign = {_CLASS = 'CallSign'} do
+
+function CallSign.new (value)
+    local obj = {value  = value}
+    return setmetatable(obj, {__index = CallSign})
+end
+
+function CallSign:compare_to (other)
+    return (self.value == other.value) and 0 or ((self.value < other.value) and -1 or 1)
+end
+
+end -- class CallSign
+
+local Collision = {_CLASS = 'Collision'} do
+
+function Collision.new (aircraft_a, aircraft_b, position)
+    local obj = {
+        aircraft_a = aircraft_a,
+        aircraft_b = aircraft_b,
+        position   = position
+    }
+    return setmetatable(obj, {__index = Collision})
+end
+
+end -- class Collision
+
+local Motion = {_CLASS = 'Motion'} do
+
+local sqrt = math.sqrt
+
+function Motion.new (callsign, pos_one, pos_two)
+    local obj = {
+        callsign = callsign,
+        pos_one = pos_one,
+        pos_two = pos_two,
+    }
+    return setmetatable(obj, {__index = Motion})
+end
+
+function Motion:delta ()
+    return self.pos_two:minus(self.pos_one)
+end
+
+function Motion:find_intersection (other)
+    local init1 = self.pos_one
+    local init2 = other.pos_one
+    local vec1 = self:delta()
+    local vec2 = other:delta()
+    local radius = PROXIMITY_RADIUS
+
+    -- this test is not geometrical 3-d intersection test,
+    -- it takes the fact that the aircraft move
+    -- into account; so it is more like a 4d test
+    -- (it assumes that both of the aircraft have a constant speed
+    -- over the tested interval)
+
+    -- we thus have two points,
+    -- each of them moving on its line segment at constant speed;
+    -- we are looking for times when the distance between
+    -- these two points is smaller than r
+
+    -- vec1 is vector of aircraft 1
+    -- vec2 is vector of aircraft 2
+
+    -- a = (V2 - V1)^T * (V2 - V1)
+    local a = vec2:minus(vec1):squared_magnitude()
+
+    if a ~= 0.0 then
+        -- we are first looking for instances
+        -- of time when the planes are exactly r from each other
+        -- at least one plane is moving;
+        -- if the planes are moving in parallel, they do not have constant speed
+
+        -- if the planes are moving in parallel, then
+        --   if the faster starts behind the slower,
+        --     we can have 2, 1, or 0 solutions
+        --   if the faster plane starts in front of the slower,
+        --     we can have 0 or 1 solutions
+
+        -- if the planes are not moving in parallel, then
+
+        -- point P1 = I1 + vV1
+        -- point P2 = I2 + vV2
+        --   - looking for v, such that dist(P1,P2) = || P1 - P2 || = r
+
+        -- it follows that || P1 - P2 || = sqrt( < P1-P2, P1-P2 > )
+        --   0 = -r^2 + < P1 - P2, P1 - P2 >
+        --  from properties of dot product
+        --   0 = -r^2 + <I1-I2,I1-I2> + v * 2<I1-I2, V1-V2> + v^2 *<V1-V2,V1-V2>
+        --   so we calculate a, b, c - and solve the quadratic equation
+        --   0 = c + bv + av^2
+
+        -- b = 2 * <I1-I2, V1-V2>
+        local b = 2.0 * init1:minus(init2):dot(vec1:minus(vec2))
+
+        -- c = -r^2 + (I2 - I1)^T * (I2 - I1)
+        local c = -radius * radius + init2:minus(init1):squared_magnitude()
+
+        local discr = b * b - 4.0 * a * c
+        if discr < 0.0 then
+            return nil
+        end
+
+        local v1 = (-b - sqrt(discr)) / (2.0 * a)
+        local v2 = (-b + sqrt(discr)) / (2.0 * a)
+
+        if v1 <= v2 and ((v1  <= 1.0 and 1.0 <= v2) or
+            (v1  <= 0.0 and 0.0 <= v2) or
+            (0.0 <= v1  and v2  <= 1.0)) then
+            -- Pick a good "time" at which to report the collision.
+            local v
+            if v1 <= 0.0 then
+                -- The collision started before this frame.
+                -- Report it at the start of the frame.
+                v = 0.0
+            else
+                -- The collision started during this frame. Report it at that moment.
+                v = v1
+            end
+
+            local result1 = init1:plus(vec1:times(v))
+            local result2 = init2:plus(vec2:times(v))
+
+            local result = result1:plus(result2):times(0.5)
+            if result.x >= MIN_X and
+               result.x <= MAX_X and
+               result.y >= MIN_Y and
+               result.y <= MAX_Y and
+               result.z >= MIN_Z and
+               result.z <= MAX_Z then
+                return result
+            end
+        end
+
+        return nil
+    end
+
+    -- the planes have the same speeds and are moving in parallel
+    -- (or they are not moving at all)
+    -- they  thus have the same distance all the time;
+    -- we calculate it from the initial point
+
+    -- dist = || i2 - i1 || = sqrt(  ( i2 - i1 )^T * ( i2 - i1 ) )
+    local dist = init2:minus(init1):magnitude()
+    if dist <= radius then
+        return init1:plus(init2):times(0.5)
+    end
+    return nil
+end
+
+end -- class Motion
+
+local CollisionDetector = {_CLASS = 'CollisionDetector'} do
+
+local floor = math.floor
+local HORIZONTAL = Vector2D.new(GOOD_VOXEL_SIZE, 0.0)
+local VERTICAL   = Vector2D.new(0.0, GOOD_VOXEL_SIZE)
+
+function CollisionDetector.new ()
+    local obj = {state = RedBlackTree.new()}
+    return setmetatable(obj, {__index = CollisionDetector})
+end
+
+function CollisionDetector:handle_new_frame (frame)
+    local motions = Vector.new()
+    local seen    = RedBlackTree.new()
+
+    frame:each(function (aircraft)
+        local old_position = self.state:put(aircraft.callsign, aircraft.position)
+        local new_position = aircraft.position
+        seen:put(aircraft.callsign, true)
+
+        if not old_position then
+            -- Treat newly introduced aircraft as if they were stationary.
+            old_position = new_position
+        end
+
+        motions:append(Motion.new(aircraft.callsign, old_position, new_position))
+    end)
+
+    -- Remove aircraft that are no longer present.
+    local to_remove = Vector.new()
+    self.state:for_each(function (e)
+        if not seen:get(e.key) then
+            to_remove:append(e.key)
+        end
+    end)
+
+    to_remove:each(function (e)
+        self.state:remove(e)
+    end)
+
+    local all_reduced = self:reduce_collision_set(motions)
+    local collisions = Vector.new()
+    all_reduced:each(function (reduced)
+        for i = 1, reduced:size() do
+            local motion1 = reduced:at(i)
+            for j = i + 1, reduced:size() do
+                local motion2 = reduced:at(j)
+                local collision = motion1:find_intersection(motion2)
+                if collision then
+                    collisions:append(Collision.new(motion1.callsign,
+                                                    motion2.callsign,
+                                                    collision))
+                end
+            end
+        end
+    end)
+
+    return collisions
+end
+
+function CollisionDetector:is_in_voxel (voxel, motion)
+    if voxel.x > MAX_X or
+       voxel.x < MIN_X or
+       voxel.y > MAX_Y or
+       voxel.y < MIN_Y then
+        return false
+    end
+
+    local init = motion.pos_one
+    local fin  = motion.pos_two
+
+    local v_s = GOOD_VOXEL_SIZE
+    local r   = PROXIMITY_RADIUS / 2.0
+
+    local v_x = voxel.x
+    local x0 = init.x
+    local xv = fin.x - init.x
+
+    local v_y = voxel.y
+    local y0 = init.y
+    local yv = fin.y - init.y
+
+    local low_x  = (v_x - r - x0) / xv
+    local high_x = (v_x + v_s + r - x0) / xv
+
+    if xv < 0.0 then
+        local tmp = low_x
+        low_x = high_x
+        high_x = tmp
+    end
+
+    local low_y  = (v_y - r - y0) / yv
+    local high_y = (v_y + v_s + r - y0) / yv
+
+    if yv < 0.0 then
+        local tmp = low_y
+        low_y = high_y
+        high_y = tmp
+    end
+
+    return (((xv == 0.0 and v_x <= x0 + r and x0 - r <= v_x + v_s) or -- no motion in x
+            (low_x <= 1.0 and 1.0 <= high_x) or (low_x <= 0.0 and 0.0 <= high_x) or
+             (0.0 <= low_x and high_x <= 1.0)) and
+           ((yv == 0.0 and v_y <= y0 + r and y0 - r <= v_y + v_s) or -- no motion in y
+            ((low_y <= 1.0 and 1.0 <= high_y) or (low_y <= 0.0 and 0.0 <= high_y) or
+             (0.0 <= low_y and high_y <= 1.0))) and
+           (xv == 0.0 or yv == 0.0 or -- no motion in x or y or both
+            (low_y <= high_x and high_x <= high_y) or
+            (low_y <= low_x and low_x <= high_y) or
+            (low_x <= low_y and high_y <= high_x)))
+end
+
+function CollisionDetector:put_into_map (voxel_map, voxel, motion)
+    local array = voxel_map:get(voxel)
+    if not array then
+        array = Vector.new()
+        voxel_map:put(voxel, array)
+    end
+    array:append(motion)
+end
+
+function CollisionDetector:recurse (voxel_map, seen, next_voxel, motion)
+    if not self:is_in_voxel(next_voxel, motion) then
+        return
+    end
+    if seen:put(next_voxel, true) then
+        return
+    end
+
+    self:put_into_map(voxel_map, next_voxel, motion)
+
+    self:recurse(voxel_map, seen, next_voxel:minus(HORIZONTAL), motion)
+    self:recurse(voxel_map, seen, next_voxel:plus(HORIZONTAL),  motion)
+    self:recurse(voxel_map, seen, next_voxel:minus(VERTICAL),   motion)
+    self:recurse(voxel_map, seen, next_voxel:plus(VERTICAL),    motion)
+    self:recurse(voxel_map, seen, next_voxel:minus(HORIZONTAL):minus(VERTICAL), motion)
+    self:recurse(voxel_map, seen, next_voxel:minus(HORIZONTAL):plus(VERTICAL),  motion)
+    self:recurse(voxel_map, seen, next_voxel:plus(HORIZONTAL):minus(VERTICAL),  motion)
+    self:recurse(voxel_map, seen, next_voxel:plus(HORIZONTAL):plus(VERTICAL),   motion)
+end
+
+function CollisionDetector:reduce_collision_set (motions)
+    local voxel_map = RedBlackTree.new()
+    motions:each(function (motion)
+        self:draw_motion_on_voxel_map(voxel_map, motion)
+    end)
+
+    local result = Vector.new()
+    voxel_map:for_each(function (e)
+        if e.value:size() > 1 then
+            result:append(e.value)
+        end
+    end)
+    return result
+end
+
+function CollisionDetector:voxel_hash (position)
+    local x_div = floor(position.x / GOOD_VOXEL_SIZE)
+    local y_div = floor(position.y / GOOD_VOXEL_SIZE)
+
+    local x = GOOD_VOXEL_SIZE * x_div
+    local y = GOOD_VOXEL_SIZE * y_div
+
+    if position.x < 0 then
+        x = x - GOOD_VOXEL_SIZE
+    end
+    if position.y < 0 then
+        y = y - GOOD_VOXEL_SIZE
+    end
+
+    return Vector2D.new(x, y)
+end
+
+function CollisionDetector:draw_motion_on_voxel_map (voxel_map, motion)
+    local seen = RedBlackTree.new()
+    return self:recurse(voxel_map, seen, self:voxel_hash(motion.pos_one), motion)
+end
+
+end -- class CollisionDetector
+
+local Aircraft = {_CLASS = 'Aircraft'} do
+
+function Aircraft.new (callsign, position)
+    local obj = {
+        callsign = callsign,
+        position = position
+    }
+    return setmetatable(obj, {__index = Aircraft})
+end
+
+end -- class Collision
+
+local Simulator = {_CLASS = 'Simulator'} do
+
+local cos = math.cos
+local sin = math.sin
+
+function Simulator.new (num_aircrafts)
+    local aircraft = Vector.new()
+    for i = 1, num_aircrafts do
+        aircraft:append(CallSign.new(i))
+    end
+    local obj = {aircraft = aircraft}
+    return setmetatable(obj, {__index = Simulator})
+end
+
+function Simulator:simulate (time)
+    local frame = Vector.new()
+    for i = 1, self.aircraft:size() - 1, 2 do
+        frame:append(Aircraft.new(self.aircraft:at(i),
+                                  Vector3D.new(time,
+                                               cos(time) * 2 + (i - 1) * 3,
+                                               10.0)))
+        frame:append(Aircraft.new(self.aircraft:at(i + 1),
+                                  Vector3D.new(time,
+                                               sin(time) * 2 + (i - 1) * 3,
+                                               10.0)))
+    end
+    return frame
+end
+
+end -- class Simulator
+
+local cd = {} do
+setmetatable(cd, {__index = benchmark})
+
+function cd:benchmark (num_aircrafts)
+    local num_frames = 200
+    local simulator  = Simulator.new(num_aircrafts)
+    local detector   = CollisionDetector.new()
+
+    local actual_collisions = 0
+    for i = 0, num_frames - 1 do
+        local time = i / 10.0
+        local collisions = detector:handle_new_frame(simulator:simulate(time))
+        actual_collisions = actual_collisions + collisions:size()
+    end
+    return actual_collisions
+end
+
+function cd:inner_benchmark_loop (inner_iterations)
+    return self:verify_result(self:benchmark(inner_iterations), inner_iterations)
+end
+
+function cd:verify_result (actual_collisions, num_aircrafts)
+    if     num_aircrafts == 1000 then
+        return actual_collisions == 14484
+    elseif num_aircrafts ==  500 then
+        return actual_collisions == 14484
+    elseif num_aircrafts ==  250 then
+        return actual_collisions == 10830
+    elseif num_aircrafts ==  200 then
+        return actual_collisions ==  8655
+    elseif num_aircrafts ==  100 then
+        return actual_collisions ==  4305
+    elseif num_aircrafts ==   10 then
+        return actual_collisions ==   390
+    elseif num_aircrafts ==    2 then
+        return actual_collisions ==    42
+    else
+        print(('No verification result for %d found'):format(num_aircrafts))
+        print(('Result is: %d'):format(actual_collisions))
+        return false
+    end
+end
+
+end -- object cd
+
+return function(N)
+    cd:inner_benchmark_loop(N)
+end

+ 1251 - 0
experiments/havlak.lua

@@ -0,0 +1,1251 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local BasicBlock = {_CLASS = 'BasicBlock'} do
+
+function BasicBlock.new (name)
+    local obj = {
+        name = name,
+        in_edges  = Vector.new(2),
+        out_edges = Vector.new(2),
+    }
+    return setmetatable(obj, {__index = BasicBlock})
+end
+
+function BasicBlock:num_pred ()
+    return self.in_edges:size()
+end
+
+function BasicBlock:add_out_edge (to)
+    self.out_edges:append(to)
+end
+
+function BasicBlock:add_in_edge (from)
+    self.in_edges:append(from)
+end
+
+function BasicBlock:custom_hash ()
+    return self.name
+end
+
+end -- class BasicBlock
+
+local BasicBlockEdge = {_CLASS = 'BasicBlockEdge'} do
+
+function BasicBlockEdge.new (cfg, from_name, to_name)
+    local from = cfg:create_node(from_name)
+    local to   = cfg:create_node(to_name)
+
+    from:add_out_edge(to)
+    to:add_in_edge(from)
+    local obj = {
+        from = from,
+        to   = to,
+    }
+    setmetatable(obj, {__index = BasicBlockEdge})
+
+    cfg:add_edge(obj)
+    return obj
+end
+
+end -- class BasicBlockEdge
+
+local ControlFlowGraph = {_CLASS = 'ControlFlowGraph'} do
+
+function ControlFlowGraph.new ()
+    local obj = {
+        start_node = nil,
+        basic_block_map = Vector.new(),
+        edge_list = Vector.new(),
+    }
+    return setmetatable(obj, {__index = ControlFlowGraph})
+end
+
+function ControlFlowGraph:create_node (name)
+    local node
+    if self.basic_block_map:at(name) then
+        node = self.basic_block_map:at(name)
+    else
+        node = BasicBlock.new(name)
+        self.basic_block_map:at_put(name, node)
+    end
+    if self:num_nodes() == 1 then
+        self.start_node = node
+    end
+    return node
+end
+
+function ControlFlowGraph:add_edge (edge)
+    self.edge_list:append(edge)
+end
+
+function ControlFlowGraph:num_nodes ()
+    return self.basic_block_map:size()
+end
+
+function ControlFlowGraph:get_start_basic_block ()
+    return self.start_node
+end
+
+function ControlFlowGraph:get_basic_blocks ()
+    return self.basic_block_map
+end
+
+end -- class ControlFlowGraph
+
+local SimpleLoop = {_CLASS = 'SimpleLoop'} do
+
+function SimpleLoop.new (bb, is_reducible)
+    local obj = {
+        header        = bb,
+        is_reducible  = is_reducible,
+        parent        = nil,
+        is_root       = false,
+        nesting_level = 0,
+        depth_level   = 0,
+        counter       = 0,
+        basic_blocks  = IdentitySet.new(),
+        children      = IdentitySet.new(),
+    }
+    if bb then
+        obj.basic_blocks:add(bb)
+    end
+    return setmetatable(obj, {__index = SimpleLoop})
+end
+
+function SimpleLoop:add_node (bb)
+    self.basic_blocks:add(bb)
+end
+
+function SimpleLoop:add_child_loop (loop)
+    self.children:add(loop)
+end
+
+function SimpleLoop:set_parent (parent)
+    self.parent = parent
+    self.parent:add_child_loop(self)
+end
+
+function SimpleLoop:set_is_root ()
+    self.is_root = true
+end
+
+function SimpleLoop:set_nesting_level (level)
+    self.nesting_level = level
+    if level == 0 then
+        self:set_is_root()
+    end
+end
+
+end -- class SimpleLoop
+
+local LoopStructureGraph = {_CLASS = 'LoopStructureGraph'} do
+
+function LoopStructureGraph.new ()
+    local loops = Vector.new()
+    local root = SimpleLoop.new(nil, true)
+    local obj = {
+        loop_counter = 0,
+        loops = loops,
+        root = root,
+    }
+    root:set_nesting_level(0)
+    root.counter = obj.loop_counter
+    obj.loop_counter = obj.loop_counter + 1
+    loops:append(root)
+    return setmetatable(obj, {__index = LoopStructureGraph})
+end
+
+function LoopStructureGraph:create_new_loop (bb, is_reducible)
+    local loop = SimpleLoop.new(bb, is_reducible)
+    loop.counter = self.loop_counter
+    self.loop_counter = self.loop_counter + 1
+    self.loops:append(loop)
+    return loop
+end
+
+function LoopStructureGraph:calculate_nesting_level ()
+    -- link up all 1st level loops to artificial root node.
+    self.loops:each(function (it)
+        if not it.is_root then
+            if not it.parent then
+                it:set_parent(self.root)
+            end
+        end
+    end)
+    -- recursively traverse the tree and assign levels.
+    self:calculate_nesting_level_rec(self.root, 0)
+end
+
+local function max (a, b)
+    return (a < b) and b or a
+end
+
+function LoopStructureGraph:calculate_nesting_level_rec (loop, depth)
+    loop.depth_level = depth
+    loop.children:each(function (it)
+        self:calculate_nesting_level_rec(it, depth + 1)
+        loop:set_nesting_level(max(loop.nesting_level, 1 + it.nesting_level))
+    end)
+end
+
+function LoopStructureGraph:num_loops ()
+    return self.loops:size()
+end
+
+end -- class LoopStructureGraph
+
+local UnionFindNode = {_CLASS = 'UnionFindNode'} do
+
+function UnionFindNode.new ()
+    local obj = {
+        dfs_number = 0,
+        parent = nil,
+        bb     = nil,
+        loop   = nil,
+    }
+    return setmetatable(obj, {__index = UnionFindNode})
+end
+
+function UnionFindNode:init_node (bb, dfs_number)
+    self.parent     = self
+    self.bb         = bb
+    self.dfs_number = dfs_number
+    self.loop       = nil
+end
+
+function UnionFindNode:find_set ()
+    local node_list = Vector.new()
+
+    local node = self
+    while node ~= node.parent do
+        if node.parent ~= node.parent.parent then
+            node_list:append(node)
+        end
+        node = node.parent
+    end
+
+    -- Path Compression, all nodes' parents point to the 1st level parent.
+    node_list:each(function (it)
+        it:union(self.parent)
+    end)
+    return node
+end
+
+function UnionFindNode:union (basic_block)
+    self.parent = basic_block
+end
+
+end -- class UnionFindNode
+
+local HavlakLoopFinder = {_CLASS = 'HavlakLoopFinder'} do
+
+local UNVISITED = 2147483647            -- Marker for uninitialized nodes.
+local MAXNONBACKPREDS = 32 * 1024       -- Safeguard against pathological algorithm behavior.
+
+function HavlakLoopFinder.new (cfg, lsg)
+    local obj = {
+        cfg = cfg,
+        lsg = lsg,
+        non_back_preds = Vector.new(),
+        back_preds     = Vector.new(),
+        number         = IdentityDictionary.new(),
+        max_size = 0,
+        header = nil,
+        type   = nil,
+        last   = nil,
+        nodes  = nil,
+    }
+    return setmetatable(obj, {__index = HavlakLoopFinder})
+end
+
+-- As described in the paper, determine whether a node 'w' is a
+-- "true" ancestor for node 'v'.
+--
+-- Dominance can be tested quickly using a pre-order trick
+-- for depth-first spanning trees. This is why DFS is the first
+-- thing we run below.
+function HavlakLoopFinder:is_ancestor (w, v)
+    return (w <= v) and (v <= self.last[w])
+end
+
+-- DFS - Depth-First-Search
+--
+-- DESCRIPTION:
+-- Simple depth first traversal along out edges with node numbering.
+function HavlakLoopFinder:do_dfs (current_node, current)
+    self.nodes[current]:init_node(current_node, current)
+    self.number:at_put(current_node, current)
+
+    local last_id = current
+    local outer_blocks = current_node.out_edges
+
+    outer_blocks:each(function (target)
+        if self.number:at(target) == UNVISITED then
+            last_id = self:do_dfs(target, last_id + 1)
+        end
+    end)
+
+    self.last[current] = last_id
+    return last_id
+end
+
+function HavlakLoopFinder:init_all_nodes ()
+    -- Step a:
+    --   - initialize all nodes as unvisited.
+    --   - depth-first traversal and numbering.
+    --   - unreached BB's are marked as dead.
+    self.cfg:get_basic_blocks():each(function (bb)
+        self.number:at_put(bb, UNVISITED)
+    end)
+    self:do_dfs(self.cfg:get_start_basic_block(), 1)
+end
+
+function HavlakLoopFinder:identify_edges (size)
+    -- Step b:
+    --   - iterate over all nodes.
+    --
+    --   A backedge comes from a descendant in the DFS tree, and non-backedges
+    --   from non-descendants (following Tarjan).
+    --
+    --   - check incoming edges 'v' and add them to either
+    --     - the list of backedges (backPreds) or
+    --     - the list of non-backedges (nonBackPreds)
+    for w = 1, size do
+        self.header[w] = 1
+        self.type[w] = 'BB_NONHEADER'
+
+        local node_w = self.nodes[w].bb
+        if not node_w then
+            self.type[w] = 'BB_DEAD'
+        else
+            self:process_edges(node_w, w)
+        end
+    end
+end
+
+function HavlakLoopFinder:process_edges (node_w, w)
+    local number = self.number
+    if node_w:num_pred() > 0 then
+        node_w.in_edges:each(function (node_v)
+            local v = number:at(node_v)
+            if v ~= UNVISITED then
+                if self:is_ancestor(w, v) then
+                    self.back_preds:at(w):append(v)
+                else
+                    self.non_back_preds:at(w):add(v)
+                end
+            end
+        end)
+    end
+end
+
+-- Find loops and build loop forest using Havlak's algorithm, which
+-- is derived from Tarjan. Variable names and step numbering has
+-- been chosen to be identical to the nomenclature in Havlak's
+-- paper (which, in turn, is similar to the one used by Tarjan).
+function HavlakLoopFinder:find_loops ()
+    if not self.cfg:get_start_basic_block() then
+        return
+    end
+
+    local size = self.cfg:num_nodes()
+    self.non_back_preds:remove_all()
+    self.back_preds:remove_all()
+    self.number:remove_all()
+
+    if size > self.max_size then
+        self.header   = {}
+        self.type     = {}
+        self.last     = {}
+        self.nodes    = {}
+        self.max_size = size
+    end
+
+    for i = 1, size do
+        self.non_back_preds:append(Set.new())
+        self.back_preds:append(Vector.new())
+        self.nodes[i] = UnionFindNode.new()
+    end
+
+    self:init_all_nodes()
+    self:identify_edges(size)
+
+    -- Start node is root of all other loops.
+    self.header[0] = 0
+
+    -- Step c:
+    --
+    -- The outer loop, unchanged from Tarjan. It does nothing except
+    -- for those nodes which are the destinations of backedges.
+    -- For a header node w, we chase backward from the sources of the
+    -- backedges adding nodes to the set P, representing the body of
+    -- the loop headed by w.
+    --
+    -- By running through the nodes in reverse of the DFST preorder,
+    -- we ensure that inner loop headers will be processed before the
+    -- headers for surrounding loops.
+    for w = size, 1, -1 do
+        -- this is 'P' in Havlak's paper
+        local node_pool = Vector.new()
+        local node_w = self.nodes[w].bb
+        if node_w then
+            self:step_d(w, node_pool)
+
+            -- Copy nodePool to workList.
+            local work_list = Vector.new()
+            node_pool:each(function (it)
+                work_list:append(it)
+            end)
+
+            if node_pool:size() ~= 0 then
+                self.type[w] = 'BB_REDUCIBLE'
+            end
+
+            -- work the list...
+            while not work_list:is_empty() do
+                local x = work_list:remove_first()
+
+                -- Step e:
+                --
+                -- Step e represents the main difference from Tarjan's method.
+                -- Chasing upwards from the sources of a node w's backedges. If
+                -- there is a node y' that is not a descendant of w, w is marked
+                -- the header of an irreducible loop, there is another entry
+                -- into this loop that avoids w.
+
+                -- The algorithm has degenerated. Break and
+                -- return in this case.
+                local non_back_size = self.non_back_preds:at(x.dfs_number):size()
+                if non_back_size > MAXNONBACKPREDS then
+                    return
+                end
+                self:step_e_process_non_back_preds(w, node_pool, work_list, x)
+            end
+        end
+
+        -- Collapse/Unionize nodes in a SCC to a single node
+        -- For every SCC found, create a loop descriptor and link it in.
+        if (node_pool:size() > 0) or (self.type[w] == 'BB_SELF') then
+            local loop = self.lsg:create_new_loop(node_w, self.type[w] ~= 'BB_IRREDUCIBLE')
+            self:set_loop_attributes(w, node_pool, loop)
+        end
+    end
+end
+
+function HavlakLoopFinder:step_e_process_non_back_preds (w, node_pool, work_list, x)
+    self.non_back_preds:at(x.dfs_number):each(function (it)
+        local y = self.nodes[it]
+        local ydash = y:find_set()
+
+        if not self:is_ancestor(w, ydash.dfs_number) then
+            self.type[w] = 'BB_IRREDUCIBLE'
+            self.non_back_preds:at(w):add(ydash.dfs_number)
+        else
+            if ydash.dfs_number ~= w then
+                if not node_pool:has_some(function (e) return e == ydash end) then
+                    work_list:append(ydash)
+                    node_pool:append(ydash)
+                end
+            end
+        end
+    end)
+end
+
+function HavlakLoopFinder:set_loop_attributes (w, node_pool, loop)
+    -- At this point, one can set attributes to the loop, such as:
+    --
+    -- the bottom node:
+    --    iter  = backPreds[w].begin();
+    --    loop bottom is: nodes[iter].node);
+    --
+    -- the number of backedges:
+    --    backPreds[w].size()
+    --
+    -- whether this loop is reducible:
+    --    type[w] != BasicBlockClass.BB_IRREDUCIBLE
+    self.nodes[w].loop = loop
+    node_pool:each(function (node)
+        -- Add nodes to loop descriptor.
+        self.header[node.dfs_number] = w
+        node:union(self.nodes[w])
+
+        -- Nested loops are not added, but linked together.
+        if node.loop then
+            node.loop:set_parent(loop)
+        else
+            loop:add_node(node.bb)
+        end
+    end)
+end
+
+function HavlakLoopFinder:step_d (w, node_pool)
+    self.back_preds:at(w):each(function (v)
+        if v ~= w then
+            node_pool:append(self.nodes[v]:find_set())
+        else
+            self.type[w] = 'BB_SELF'
+        end
+    end)
+end
+
+end -- class HavlakLoopFinder
+
+local LoopTesterApp = {_CLASS = 'LoopTesterApp'} do
+
+function LoopTesterApp.new ()
+    local cfg = ControlFlowGraph.new()
+    local lsg = LoopStructureGraph.new()
+    local obj = {
+        cfg = cfg,
+        lsg = lsg,
+    }
+    cfg:create_node(1)
+    return setmetatable(obj, {__index = LoopTesterApp})
+end
+
+-- Create 4 basic blocks, corresponding to and if/then/else clause
+-- with a CFG that looks like a diamond
+function LoopTesterApp:build_diamond (start)
+    local bb0 = start
+    BasicBlockEdge.new(self.cfg, bb0, bb0 + 1)
+    BasicBlockEdge.new(self.cfg, bb0, bb0 + 2)
+    BasicBlockEdge.new(self.cfg, bb0 + 1, bb0 + 3)
+    BasicBlockEdge.new(self.cfg, bb0 + 2, bb0 + 3)
+    return bb0 + 3
+end
+
+-- Connect two existing nodes
+function LoopTesterApp:build_connect (start, end_)
+    BasicBlockEdge.new(self.cfg, start, end_)
+end
+
+-- Form a straight connected sequence of n basic blocks
+function LoopTesterApp:build_straight (start, n)
+    for i = 1, n do
+        self:build_connect(start + i - 1, start + i)
+    end
+    return start + n
+end
+
+-- Construct a simple loop with two diamonds in it
+function LoopTesterApp:build_base_loop (from)
+    local header   = self:build_straight(from, 1)
+    local diamond1 = self:build_diamond(header)
+    local d11      = self:build_straight(diamond1, 1)
+    local diamond2 = self:build_diamond(d11)
+    local footer   = self:build_straight(diamond2, 1)
+    self:build_connect(diamond2, d11)
+    self:build_connect(diamond1, header)
+    self:build_connect(footer, from)
+    return self:build_straight(footer, 1)
+end
+
+function LoopTesterApp:main (num_dummy_loops, find_loop_iterations, par_loops,
+                             ppar_loops, pppar_loops)
+    self:construct_simple_cfg()
+    self:add_dummy_loops(num_dummy_loops)
+    self:construct_cfg(par_loops, ppar_loops, pppar_loops)
+
+    -- Performing Loop Recognition, 1 Iteration, then findLoopIteration
+    self:find_loops(self.lsg)
+
+    for _ = 0, find_loop_iterations do
+        self:find_loops(LoopStructureGraph.new())
+    end
+
+    self.lsg:calculate_nesting_level()
+    return {self.lsg:num_loops(), self.cfg:num_nodes()}
+end
+
+function LoopTesterApp:construct_cfg (par_loops, ppar_loops, pppar_loops)
+    local n = 3
+
+    for _ = 1, par_loops do
+        self.cfg:create_node(n + 1)
+        self:build_connect(3, n + 1)
+        n = n + 1
+
+        for _ = 1, ppar_loops do
+            local top = n
+            n = self:build_straight(n, 1)
+            for _ = 1, pppar_loops do
+                n = self:build_base_loop(n)
+            end
+            local bottom = self:build_straight(n, 1)
+            self:build_connect(n, top)
+            n = bottom
+        end
+        self:build_connect(n, 1)
+    end
+end
+
+function LoopTesterApp:add_dummy_loops (num_dummy_loops)
+    for _ = 1, num_dummy_loops do
+        self:find_loops(self.lsg)
+    end
+end
+
+function LoopTesterApp:find_loops (loop_structure)
+    local finder = HavlakLoopFinder.new(self.cfg, loop_structure)
+    finder:find_loops()
+end
+
+function LoopTesterApp:construct_simple_cfg ()
+    self.cfg:create_node(1)
+    self:build_base_loop(1)
+    self.cfg:create_node(2)
+    BasicBlockEdge.new(self.cfg, 1, 3)
+end
+
+end -- class LoopTesterApp
+
+
+local havlak = {} do
+setmetatable(havlak, {__index = benchmark})
+
+function havlak:inner_benchmark_loop (inner_iterations)
+    local result = LoopTesterApp.new():main(inner_iterations, 50, 10, 10, 5)
+    return self:verify_result(result, inner_iterations)
+end
+
+function havlak:verify_result (result, inner_iterations)
+    if     inner_iterations == 15000 then
+        return result[1] == 46602 and result[2] == 5213
+    elseif inner_iterations ==  1500 then
+        return result[1] ==  6102 and result[2] == 5213
+    elseif inner_iterations ==   150 then
+        return result[1] ==  2052 and result[2] == 5213
+    elseif inner_iterations ==    15 then
+        return result[1] ==  1647 and result[2] == 5213
+    elseif inner_iterations ==     1 then
+        return result[1] ==  1605 and result[2] == 5213
+    else
+        print(('No verification result for %d found'):format(inner_iterations))
+        print(('Result is: %d, %d'):format(result[0], result[1]))
+        return false
+    end
+end
+
+end -- object havlak
+
+return function(N)
+    havlak:inner_benchmark_loop(N)
+end

文件差异内容过多而无法显示
+ 1196 - 0
experiments/json.lua


文件差异内容过多而无法显示
+ 1200 - 0
experiments/json_jit.lua


+ 688 - 0
experiments/list.lua

@@ -0,0 +1,688 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local Element = {_CLASS = 'Element'} do
+
+function Element.new (v)
+    local obj = {val = v, next = nil}
+    return setmetatable(obj, {__index = Element})
+end
+
+function Element:length ()
+    if not self.next then
+        return 1
+    else
+        return 1 + self.next:length()
+    end
+end
+
+end -- class Element
+
+local list = {} do
+setmetatable(list, {__index = benchmark})
+
+function list:benchmark ()
+    local result = self:tail(self:make_list(15),
+                             self:make_list(10),
+                             self:make_list(6))
+    return result:length()
+end
+
+function list:make_list (length)
+    if length == 0 then
+        return nil
+    else
+        local e = Element.new(length)
+        e.next = self:make_list(length - 1)
+        return e
+    end
+end
+
+function list:is_shorter_than (x, y)
+    local x_tail, y_tail = x, y
+    while y_tail do
+        if not x_tail then
+            return true
+        end
+        x_tail = x_tail.next
+        y_tail = y_tail.next
+    end
+    return false
+end
+
+function list:tail (x, y, z)
+    if self:is_shorter_than(y, x) then
+        return self:tail(self:tail(x.next, y, z),
+                         self:tail(y.next, z, x),
+                         self:tail(z.next, x, y))
+    else
+        return z
+    end
+end
+
+function list:verify_result (result)
+    return 10 == result
+end
+
+end -- object list
+
+return function(N)
+    list:inner_benchmark_loop(N)
+end

+ 658 - 0
experiments/permute.lua

@@ -0,0 +1,658 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local permute = {} do
+setmetatable(permute, {__index = benchmark})
+
+function permute:benchmark ()
+    self.count = 0
+    self.v = {0, 0, 0, 0, 0, 0}
+    self:permute(6)
+    return self.count
+end
+
+function permute:verify_result (result)
+    return 8660 == result
+end
+
+function permute:permute (n)
+    self.count = self.count + 1
+    if n ~= 0 then
+        local n1 = n - 1
+        self:permute(n1)
+        for i = n, 1, -1 do
+            self:swap(n, i)
+            self:permute(n1)
+            self:swap(n, i)
+        end
+    end
+end
+
+function permute:swap (i, j)
+    local tmp = self.v[i]
+    self.v[i] = self.v[j]
+    self.v[j] = tmp
+end
+
+end -- object permute
+
+return function(N)
+    permute:inner_benchmark_loop(N)
+end

+ 1115 - 0
experiments/richards.lua

@@ -0,0 +1,1115 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local NO_TASK = nil
+local NO_WORK = nil
+
+local IDLER     = 1
+local WORKER    = 2
+local HANDLER_A = 3
+local HANDLER_B = 4
+local DEVICE_A  = 5
+local DEVICE_B  = 6
+
+local TRACING = false
+
+local RBObject = {_CLASS = 'RBObject'} do
+
+function RBObject:append (packet, queue_head)
+    packet.link = NO_WORK
+    if NO_WORK == queue_head then
+        return packet
+    end
+
+    local mouse = queue_head
+
+    local link = mouse.link
+    while NO_WORK ~= link do
+        mouse = link
+        link = mouse.link
+    end
+
+    mouse.link = packet
+    return queue_head
+end
+
+end -- abstract RBObject
+
+local DeviceTaskDataRecord = {_CLASS = 'DeviceTaskDataRecord'} do
+setmetatable(DeviceTaskDataRecord, {__index = RBObject})
+
+function DeviceTaskDataRecord.new ()
+    local obj = {
+        pending = NO_WORK,
+    }
+    return setmetatable(obj, {__index = DeviceTaskDataRecord})
+end
+
+end -- class DeviceTaskDataRecord
+
+local HandlerTaskDataRecord = {_CLASS = 'HandlerTaskDataRecord'} do
+setmetatable(HandlerTaskDataRecord, {__index = RBObject})
+
+function HandlerTaskDataRecord.new ()
+    local obj = {
+        work_in   = NO_WORK,
+        device_in = NO_WORK,
+    }
+    return setmetatable(obj, {__index = HandlerTaskDataRecord})
+end
+
+function HandlerTaskDataRecord:device_in_add (packet)
+    self.device_in = self:append(packet, self.device_in)
+end
+
+function HandlerTaskDataRecord:work_in_add (packet)
+    self.work_in = self:append(packet, self.work_in)
+end
+
+end -- class HandlerTaskDataRecord
+
+local IdleTaskDataRecord = {_CLASS = 'IdleTaskDataRecord'} do
+setmetatable(IdleTaskDataRecord, {__index = RBObject})
+
+function IdleTaskDataRecord.new ()
+    local obj = {
+        control = 1,
+        count   = 10000,
+    }
+    return setmetatable(obj, {__index = IdleTaskDataRecord})
+end
+
+end -- class IdleTaskDataRecord
+
+local WorkerTaskDataRecord = {_CLASS = 'WorkerTaskDataRecord'} do
+setmetatable(WorkerTaskDataRecord, {__index = RBObject})
+
+function WorkerTaskDataRecord.new ()
+    local obj = {
+        destination = HANDLER_A,
+        count = 0,
+    }
+    return setmetatable(obj, {__index = WorkerTaskDataRecord})
+end
+
+end -- class WorkerTaskDataRecord
+
+local Packet = {_CLASS = 'Packet'} do
+setmetatable(Packet, {__index = RBObject})
+
+function Packet.new (link, identity, kind)
+    local obj = {
+        link     = link,
+        kind     = kind,
+        identity = identity,
+        datum    = 1,
+        data     = {0, 0, 0, 0},
+    }
+    return setmetatable(obj, {__index = Packet})
+end
+
+end -- class Packet
+
+local TaskState = {_CLASS = 'TaskState'} do
+setmetatable(TaskState, {__index = RBObject})
+
+function TaskState.new ()
+    local obj = {
+        task_holding  = false,
+        task_waiting  = false,
+        packt_pending = false,
+    }
+    return setmetatable(obj, {__index = TaskState})
+end
+
+function TaskState:is_packet_pending ()
+    return self.packt_pending
+end
+
+function TaskState:is_task_waiting ()
+    return self.task_waiting
+end
+
+function TaskState:is_task_holding ()
+    return self.task_holding
+end
+
+function TaskState:packet_pending ()
+    self.packt_pending = true
+    self.task_waiting  = false
+    self.task_holding  = false
+    return self
+end
+
+function TaskState:running ()
+    self.packt_pending = false
+    self.task_waiting  = false
+    self.task_holding  = false
+    return self
+end
+
+function TaskState:waiting ()
+    self.packt_pending = false
+    self.task_holding  = false
+    self.task_waiting  = true
+    return self
+end
+
+function TaskState:waiting_with_packet ()
+    self.task_holding  = false
+    self.task_waiting  = true
+    self.packt_pending = true
+    return self
+end
+
+function TaskState:is_running ()
+    return not self.packt_pending and not self.task_waiting and not self.task_holding
+end
+
+function TaskState:is_task_holding_or_waiting ()
+    return self.task_holding or (not self.packt_pending and self.task_waiting)
+end
+
+function TaskState:is_waiting ()
+    return not self.packt_pending and self.task_waiting and not self.task_holding
+end
+
+function TaskState:is_waiting_with_packet ()
+    return self.packt_pending and self.task_waiting and not self.task_holding
+end
+
+function TaskState.create_packet_pending ()
+    return TaskState.new():packet_pending()
+end
+
+function TaskState.create_running ()
+    return TaskState.new():running()
+end
+
+function TaskState.create_waiting ()
+    return TaskState.new():waiting()
+end
+
+function TaskState.create_waiting_with_packet ()
+    return TaskState.new():waiting_with_packet()
+end
+
+end -- class TaskState
+
+local TaskControlBlock = {_CLASS = 'TaskControlBlock'} do
+setmetatable(TaskControlBlock, {__index = TaskState})
+
+function TaskControlBlock.new (link, identity, priority, initial_work_queue,
+                               initial_state, private_data, fn)
+    local obj = {
+        link = link,
+        identity = identity,
+        priority = priority,
+        input = initial_work_queue,
+        handle = private_data,
+
+        packt_pending = initial_state:is_packet_pending(),
+        task_waiting  = initial_state:is_task_waiting(),
+        task_holding  = initial_state:is_task_holding(),
+
+        fn = fn,
+    }
+    return setmetatable(obj, {__index = TaskControlBlock})
+end
+
+function TaskControlBlock:add_input_and_check_priority (packet, old_task)
+    if NO_WORK == self.input then
+        self.input = packet
+        self.packt_pending = true
+        if self.priority > old_task.priority then
+            return self
+        end
+    else
+        self.input = self:append(packet, self.input)
+    end
+    return old_task
+end
+
+function TaskControlBlock:run_task ()
+    local message
+    if self:is_waiting_with_packet() then
+        message = self.input
+        self.input = message.link
+        if NO_WORK == self.input then
+            self:running()
+        else
+            self:packet_pending()
+        end
+    else
+        message = NO_WORK
+    end
+    return self.fn(message, self.handle)
+end
+
+end -- class TaskControlBlock
+
+local Scheduler = {_CLASS = 'Scheduler'} do
+setmetatable(Scheduler, {__index = RBObject})
+
+local DEVICE_PACKET_KIND = 0
+local WORK_PACKET_KIND   = 1
+
+local DATA_SIZE = 4
+
+function Scheduler.new ()
+    local obj = {
+        -- init tracing
+        layout = 0,
+
+        -- init scheduler
+        task_list    = NO_TASK,
+        current_task = NO_TASK,
+        current_task_identity = 0,
+        task_table = {NO_TASK, NO_TASK, NO_TASK, NO_TASK, NO_TASK, NO_TASK},
+
+        queue_count = 0,
+        hold_count  = 0,
+    }
+    return setmetatable(obj, {__index = Scheduler})
+end
+
+function Scheduler:create_device (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     DeviceTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK == packet then
+            packet = data.pending
+            if NO_WORK == packet then
+                return self:wait()
+            else
+                data.pending = NO_WORK
+                return self:queue_packet(packet)
+            end
+        else
+            data.pending = packet
+            if TRACING then
+                self:trace(packet.datum)
+            end
+            return self:hold_self()
+        end
+    end)
+end
+
+function Scheduler:create_handler (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     HandlerTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK ~= packet then
+            if WORK_PACKET_KIND == packet.kind then
+                data:work_in_add(packet)
+            else
+                data:device_in_add(packet)
+            end
+        end
+
+        local work_packet = data.work_in
+        if NO_WORK == work_packet then
+            return self:wait()
+        else
+            local count = work_packet.datum
+            if count > DATA_SIZE then
+                data.work_in = work_packet.link
+                return self:queue_packet(work_packet)
+            else
+                local device_packet = data.device_in
+                if NO_WORK == device_packet then
+                    return self:wait()
+                else
+                    data.device_in = device_packet.link
+                    device_packet.datum = work_packet.data[count]
+                    work_packet.datum = count + 1
+                    return self:queue_packet(device_packet)
+                end
+            end
+        end
+    end)
+end
+
+function Scheduler:create_idler (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     IdleTaskDataRecord.new(),
+                     function (_, data)
+        data.count = data.count - 1
+        if 0 == data.count then
+            return self:hold_self()
+        else
+            if 0 == data.control & 1 then
+                data.control = data.control / 2
+                return self:release(DEVICE_A)
+            else
+                data.control = ((data.control - 1) / 2) ~ 53256
+                return self:release(DEVICE_B)
+            end
+        end
+    end)
+end
+
+function Scheduler:create_packet (link, identity, kind)
+    return Packet.new(link, identity, kind)
+end
+
+function Scheduler:create_task (identity, priority, work, state, data, fn)
+    local tcb = TaskControlBlock.new(self.task_list, identity, priority,
+                                     work, state, data, fn)
+    self.task_list = tcb
+    self.task_table[identity] = tcb
+end
+
+function Scheduler:create_worker (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     WorkerTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK == packet then
+            return self:wait()
+        else
+            data.destination = (HANDLER_A == data.destination) and HANDLER_B or HANDLER_A
+            packet.identity = data.destination
+            packet.datum = 1
+            for i = 1, DATA_SIZE do
+                data.count = data.count + 1
+                if data.count > 26 then
+                   data.count = 1
+                end
+                packet.data[i] = 65 + data.count - 1
+            end
+            return self:queue_packet(packet)
+        end
+    end)
+end
+
+function Scheduler:start ()
+    local queue
+    self:create_idler(IDLER, 0, NO_WORK, TaskState.create_running())
+    queue = self:create_packet(NO_WORK, WORKER, WORK_PACKET_KIND)
+    queue = self:create_packet(queue,   WORKER, WORK_PACKET_KIND)
+
+    self:create_worker(WORKER, 1000, queue, TaskState.create_waiting_with_packet())
+    queue = self:create_packet(NO_WORK, DEVICE_A, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_A, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_A, DEVICE_PACKET_KIND)
+
+    self:create_handler(HANDLER_A, 2000, queue, TaskState.create_waiting_with_packet())
+    queue = self:create_packet(NO_WORK, DEVICE_B, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_B, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_B, DEVICE_PACKET_KIND)
+
+    self:create_handler(HANDLER_B, 3000, queue, TaskState.create_waiting_with_packet())
+    self:create_device(DEVICE_A, 4000, NO_WORK, TaskState.create_waiting())
+    self:create_device(DEVICE_B, 5000, NO_WORK, TaskState.create_waiting())
+
+    self:schedule()
+
+    return self.queue_count == 23246 and self.hold_count == 9297
+end
+
+function Scheduler:find_task (identity)
+    local task = self.task_table[identity]
+    assert(task ~= NO_TASK, 'find_task failed')
+    return task
+end
+
+function Scheduler:hold_self ()
+    self.hold_count = self.hold_count + 1
+    local current_task = self.current_task
+    current_task.task_holding = true
+    return current_task.link
+end
+
+function Scheduler:queue_packet (packet)
+    local task = self:find_task(packet.identity)
+    if NO_TASK == task then
+        return NO_TASK
+    end
+
+    self.queue_count = self.queue_count + 1
+
+    packet.link     = NO_WORK
+    packet.identity = self.current_task_identity
+    return task:add_input_and_check_priority(packet, self.current_task)
+end
+
+function Scheduler:release (identity)
+    local task = self:find_task(identity)
+    if NO_TASK == task then
+        return NO_TASK
+    end
+
+    task.task_holding = false
+
+    if task.priority > self.current_task.priority then
+        return task
+    else
+        return self.current_task
+    end
+end
+
+function Scheduler:trace (id)
+    self.layout = self.layout - 1
+    if 0 >= self.layout then
+        io.stdout:write'\n'
+        self.layout = 50
+    end
+    io.stdout:write(tostring(id))
+end
+
+function Scheduler:wait ()
+    local current_task = self.current_task
+    current_task.task_waiting = true
+    return current_task
+end
+
+function Scheduler:schedule ()
+    self.current_task = self.task_list
+    while self.current_task ~= NO_TASK do
+        if self.current_task:is_task_holding_or_waiting() then
+            self.current_task = self.current_task.link
+        else
+            self.current_task_identity = self.current_task.identity
+            if TRACING then
+                self:trace(self.current_task_identity - 1)
+            end
+            self.current_task = self.current_task:run_task()
+        end
+    end
+end
+
+end -- class Scheduler
+
+local richards = {} do
+setmetatable(richards, {__index = benchmark})
+
+function richards:benchmark ()
+    return Scheduler.new():start()
+end
+
+function richards:verify_result (result)
+    return result
+end
+
+end -- object richards
+
+return function(N)
+    richards:inner_benchmark_loop(N)
+end

+ 1115 - 0
experiments/richards_jit.lua

@@ -0,0 +1,1115 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local NO_TASK = nil
+local NO_WORK = nil
+
+local IDLER     = 1
+local WORKER    = 2
+local HANDLER_A = 3
+local HANDLER_B = 4
+local DEVICE_A  = 5
+local DEVICE_B  = 6
+
+local TRACING = false
+
+local RBObject = {_CLASS = 'RBObject'} do
+
+function RBObject:append (packet, queue_head)
+    packet.link = NO_WORK
+    if NO_WORK == queue_head then
+        return packet
+    end
+
+    local mouse = queue_head
+
+    local link = mouse.link
+    while NO_WORK ~= link do
+        mouse = link
+        link = mouse.link
+    end
+
+    mouse.link = packet
+    return queue_head
+end
+
+end -- abstract RBObject
+
+local DeviceTaskDataRecord = {_CLASS = 'DeviceTaskDataRecord'} do
+setmetatable(DeviceTaskDataRecord, {__index = RBObject})
+
+function DeviceTaskDataRecord.new ()
+    local obj = {
+        pending = NO_WORK,
+    }
+    return setmetatable(obj, {__index = DeviceTaskDataRecord})
+end
+
+end -- class DeviceTaskDataRecord
+
+local HandlerTaskDataRecord = {_CLASS = 'HandlerTaskDataRecord'} do
+setmetatable(HandlerTaskDataRecord, {__index = RBObject})
+
+function HandlerTaskDataRecord.new ()
+    local obj = {
+        work_in   = NO_WORK,
+        device_in = NO_WORK,
+    }
+    return setmetatable(obj, {__index = HandlerTaskDataRecord})
+end
+
+function HandlerTaskDataRecord:device_in_add (packet)
+    self.device_in = self:append(packet, self.device_in)
+end
+
+function HandlerTaskDataRecord:work_in_add (packet)
+    self.work_in = self:append(packet, self.work_in)
+end
+
+end -- class HandlerTaskDataRecord
+
+local IdleTaskDataRecord = {_CLASS = 'IdleTaskDataRecord'} do
+setmetatable(IdleTaskDataRecord, {__index = RBObject})
+
+function IdleTaskDataRecord.new ()
+    local obj = {
+        control = 1,
+        count   = 10000,
+    }
+    return setmetatable(obj, {__index = IdleTaskDataRecord})
+end
+
+end -- class IdleTaskDataRecord
+
+local WorkerTaskDataRecord = {_CLASS = 'WorkerTaskDataRecord'} do
+setmetatable(WorkerTaskDataRecord, {__index = RBObject})
+
+function WorkerTaskDataRecord.new ()
+    local obj = {
+        destination = HANDLER_A,
+        count = 0,
+    }
+    return setmetatable(obj, {__index = WorkerTaskDataRecord})
+end
+
+end -- class WorkerTaskDataRecord
+
+local Packet = {_CLASS = 'Packet'} do
+setmetatable(Packet, {__index = RBObject})
+
+function Packet.new (link, identity, kind)
+    local obj = {
+        link     = link,
+        kind     = kind,
+        identity = identity,
+        datum    = 1,
+        data     = {0, 0, 0, 0},
+    }
+    return setmetatable(obj, {__index = Packet})
+end
+
+end -- class Packet
+
+local TaskState = {_CLASS = 'TaskState'} do
+setmetatable(TaskState, {__index = RBObject})
+
+function TaskState.new ()
+    local obj = {
+        task_holding  = false,
+        task_waiting  = false,
+        packt_pending = false,
+    }
+    return setmetatable(obj, {__index = TaskState})
+end
+
+function TaskState:is_packet_pending ()
+    return self.packt_pending
+end
+
+function TaskState:is_task_waiting ()
+    return self.task_waiting
+end
+
+function TaskState:is_task_holding ()
+    return self.task_holding
+end
+
+function TaskState:packet_pending ()
+    self.packt_pending = true
+    self.task_waiting  = false
+    self.task_holding  = false
+    return self
+end
+
+function TaskState:running ()
+    self.packt_pending = false
+    self.task_waiting  = false
+    self.task_holding  = false
+    return self
+end
+
+function TaskState:waiting ()
+    self.packt_pending = false
+    self.task_holding  = false
+    self.task_waiting  = true
+    return self
+end
+
+function TaskState:waiting_with_packet ()
+    self.task_holding  = false
+    self.task_waiting  = true
+    self.packt_pending = true
+    return self
+end
+
+function TaskState:is_running ()
+    return not self.packt_pending and not self.task_waiting and not self.task_holding
+end
+
+function TaskState:is_task_holding_or_waiting ()
+    return self.task_holding or (not self.packt_pending and self.task_waiting)
+end
+
+function TaskState:is_waiting ()
+    return not self.packt_pending and self.task_waiting and not self.task_holding
+end
+
+function TaskState:is_waiting_with_packet ()
+    return self.packt_pending and self.task_waiting and not self.task_holding
+end
+
+function TaskState.create_packet_pending ()
+    return TaskState.new():packet_pending()
+end
+
+function TaskState.create_running ()
+    return TaskState.new():running()
+end
+
+function TaskState.create_waiting ()
+    return TaskState.new():waiting()
+end
+
+function TaskState.create_waiting_with_packet ()
+    return TaskState.new():waiting_with_packet()
+end
+
+end -- class TaskState
+
+local TaskControlBlock = {_CLASS = 'TaskControlBlock'} do
+setmetatable(TaskControlBlock, {__index = TaskState})
+
+function TaskControlBlock.new (link, identity, priority, initial_work_queue,
+                               initial_state, private_data, fn)
+    local obj = {
+        link = link,
+        identity = identity,
+        priority = priority,
+        input = initial_work_queue,
+        handle = private_data,
+
+        packt_pending = initial_state:is_packet_pending(),
+        task_waiting  = initial_state:is_task_waiting(),
+        task_holding  = initial_state:is_task_holding(),
+
+        fn = fn,
+    }
+    return setmetatable(obj, {__index = TaskControlBlock})
+end
+
+function TaskControlBlock:add_input_and_check_priority (packet, old_task)
+    if NO_WORK == self.input then
+        self.input = packet
+        self.packt_pending = true
+        if self.priority > old_task.priority then
+            return self
+        end
+    else
+        self.input = self:append(packet, self.input)
+    end
+    return old_task
+end
+
+function TaskControlBlock:run_task ()
+    local message
+    if self:is_waiting_with_packet() then
+        message = self.input
+        self.input = message.link
+        if NO_WORK == self.input then
+            self:running()
+        else
+            self:packet_pending()
+        end
+    else
+        message = NO_WORK
+    end
+    return self.fn(message, self.handle)
+end
+
+end -- class TaskControlBlock
+
+local Scheduler = {_CLASS = 'Scheduler'} do
+setmetatable(Scheduler, {__index = RBObject})
+
+local DEVICE_PACKET_KIND = 0
+local WORK_PACKET_KIND   = 1
+
+local DATA_SIZE = 4
+
+function Scheduler.new ()
+    local obj = {
+        -- init tracing
+        layout = 0,
+
+        -- init scheduler
+        task_list    = NO_TASK,
+        current_task = NO_TASK,
+        current_task_identity = 0,
+        task_table = {NO_TASK, NO_TASK, NO_TASK, NO_TASK, NO_TASK, NO_TASK},
+
+        queue_count = 0,
+        hold_count  = 0,
+    }
+    return setmetatable(obj, {__index = Scheduler})
+end
+
+function Scheduler:create_device (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     DeviceTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK == packet then
+            packet = data.pending
+            if NO_WORK == packet then
+                return self:wait()
+            else
+                data.pending = NO_WORK
+                return self:queue_packet(packet)
+            end
+        else
+            data.pending = packet
+            if TRACING then
+                self:trace(packet.datum)
+            end
+            return self:hold_self()
+        end
+    end)
+end
+
+function Scheduler:create_handler (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     HandlerTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK ~= packet then
+            if WORK_PACKET_KIND == packet.kind then
+                data:work_in_add(packet)
+            else
+                data:device_in_add(packet)
+            end
+        end
+
+        local work_packet = data.work_in
+        if NO_WORK == work_packet then
+            return self:wait()
+        else
+            local count = work_packet.datum
+            if count > DATA_SIZE then
+                data.work_in = work_packet.link
+                return self:queue_packet(work_packet)
+            else
+                local device_packet = data.device_in
+                if NO_WORK == device_packet then
+                    return self:wait()
+                else
+                    data.device_in = device_packet.link
+                    device_packet.datum = work_packet.data[count]
+                    work_packet.datum = count + 1
+                    return self:queue_packet(device_packet)
+                end
+            end
+        end
+    end)
+end
+
+function Scheduler:create_idler (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     IdleTaskDataRecord.new(),
+                     function (_, data)
+        data.count = data.count - 1
+        if 0 == data.count then
+            return self:hold_self()
+        else
+            if 0 == band(data.control, 1) then
+                data.control = data.control / 2
+                return self:release(DEVICE_A)
+            else
+                data.control = bxor((data.control - 1) / 2, 53256)
+                return self:release(DEVICE_B)
+            end
+        end
+    end)
+end
+
+function Scheduler:create_packet (link, identity, kind)
+    return Packet.new(link, identity, kind)
+end
+
+function Scheduler:create_task (identity, priority, work, state, data, fn)
+    local tcb = TaskControlBlock.new(self.task_list, identity, priority,
+                                     work, state, data, fn)
+    self.task_list = tcb
+    self.task_table[identity] = tcb
+end
+
+function Scheduler:create_worker (identity, priority, work, state)
+    self:create_task(identity, priority, work, state,
+                     WorkerTaskDataRecord.new(),
+                     function (packet, data)
+        if NO_WORK == packet then
+            return self:wait()
+        else
+            data.destination = (HANDLER_A == data.destination) and HANDLER_B or HANDLER_A
+            packet.identity = data.destination
+            packet.datum = 1
+            for i = 1, DATA_SIZE do
+                data.count = data.count + 1
+                if data.count > 26 then
+                   data.count = 1
+                end
+                packet.data[i] = 65 + data.count - 1
+            end
+            return self:queue_packet(packet)
+        end
+    end)
+end
+
+function Scheduler:start ()
+    local queue
+    self:create_idler(IDLER, 0, NO_WORK, TaskState.create_running())
+    queue = self:create_packet(NO_WORK, WORKER, WORK_PACKET_KIND)
+    queue = self:create_packet(queue,   WORKER, WORK_PACKET_KIND)
+
+    self:create_worker(WORKER, 1000, queue, TaskState.create_waiting_with_packet())
+    queue = self:create_packet(NO_WORK, DEVICE_A, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_A, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_A, DEVICE_PACKET_KIND)
+
+    self:create_handler(HANDLER_A, 2000, queue, TaskState.create_waiting_with_packet())
+    queue = self:create_packet(NO_WORK, DEVICE_B, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_B, DEVICE_PACKET_KIND)
+    queue = self:create_packet(queue,   DEVICE_B, DEVICE_PACKET_KIND)
+
+    self:create_handler(HANDLER_B, 3000, queue, TaskState.create_waiting_with_packet())
+    self:create_device(DEVICE_A, 4000, NO_WORK, TaskState.create_waiting())
+    self:create_device(DEVICE_B, 5000, NO_WORK, TaskState.create_waiting())
+
+    self:schedule()
+
+    return self.queue_count == 23246 and self.hold_count == 9297
+end
+
+function Scheduler:find_task (identity)
+    local task = self.task_table[identity]
+    assert(task ~= NO_TASK, 'find_task failed')
+    return task
+end
+
+function Scheduler:hold_self ()
+    self.hold_count = self.hold_count + 1
+    local current_task = self.current_task
+    current_task.task_holding = true
+    return current_task.link
+end
+
+function Scheduler:queue_packet (packet)
+    local task = self:find_task(packet.identity)
+    if NO_TASK == task then
+        return NO_TASK
+    end
+
+    self.queue_count = self.queue_count + 1
+
+    packet.link     = NO_WORK
+    packet.identity = self.current_task_identity
+    return task:add_input_and_check_priority(packet, self.current_task)
+end
+
+function Scheduler:release (identity)
+    local task = self:find_task(identity)
+    if NO_TASK == task then
+        return NO_TASK
+    end
+
+    task.task_holding = false
+
+    if task.priority > self.current_task.priority then
+        return task
+    else
+        return self.current_task
+    end
+end
+
+function Scheduler:trace (id)
+    self.layout = self.layout - 1
+    if 0 >= self.layout then
+        io.stdout:write'\n'
+        self.layout = 50
+    end
+    io.stdout:write(tostring(id))
+end
+
+function Scheduler:wait ()
+    local current_task = self.current_task
+    current_task.task_waiting = true
+    return current_task
+end
+
+function Scheduler:schedule ()
+    self.current_task = self.task_list
+    while self.current_task ~= NO_TASK do
+        if self.current_task:is_task_holding_or_waiting() then
+            self.current_task = self.current_task.link
+        else
+            self.current_task_identity = self.current_task.identity
+            if TRACING then
+                self:trace(self.current_task_identity - 1)
+            end
+            self.current_task = self.current_task:run_task()
+        end
+    end
+end
+
+end -- class Scheduler
+
+local richards = {} do
+setmetatable(richards, {__index = benchmark})
+
+function richards:benchmark ()
+    return Scheduler.new():start()
+end
+
+function richards:verify_result (result)
+    return result
+end
+
+end -- object richards
+
+return function(N)
+    richards:inner_benchmark_loop(N)
+end

+ 683 - 0
experiments/towers.lua

@@ -0,0 +1,683 @@
+-- This code is derived from the SOM benchmarks, see AUTHORS.md file.
+--
+-- Copyright (c) 2016 Francois Perrad <[email protected]>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the 'Software'), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+--[[
+    The module 'bit' is available with:
+      * LuaJIT
+      * LuaBitOp extension which is available for:
+          * Lua 5.1
+          * Lua 5.2
+    The module 'bit32' is available with:
+      * Lua 5.2
+      * Lua 5.3 when compiled with LUA_COMPAT_5_2
+    The bitwise operators are added to Lua 5.3 as new lexemes (there causes
+    lexical error in older version)
+--]]
+local band, bxor, rshift
+if _VERSION < 'Lua 5.3' then
+    local bit = bit32 or require'bit'
+    band = bit.band
+    bxor = bit.bxor
+    rshift = bit.rshift
+else
+    band = assert(load'--[[band]] return function (a, b) return a & b end')()
+    bxor = assert(load'--[[bxor]] return function (a, b) return a ~ b end')()
+    rshift = assert(load'--[[rshift]] return function (a, b) return a >> b end')()
+end
+
+local alloc_array
+local ok, table_new = pcall(require, 'table.new')       -- LuaJIT 2.1 extension
+if ok then
+    alloc_array = function (n)
+        local t = table_new(n, 1)
+        t.n = n
+        return t
+    end
+else
+    alloc_array = function (n)
+        local t = {}
+        t.n = n
+        return t
+    end
+end
+
+local Vector = {_CLASS = 'Vector'} do
+
+local floor = math.floor
+
+function Vector.new (size)
+    local obj = {
+        storage   = alloc_array(size or 50),
+        first_idx = 1,
+        last_idx  = 1,
+    }
+    return setmetatable(obj, {__index = Vector})
+end
+
+function Vector.with (elem)
+    local v = Vector.new(1)
+    v:append(elem)
+    return v
+end
+
+function Vector:at (idx)
+    if idx > self.storage.n then
+        return nil
+    end
+    return self.storage[idx]
+end
+
+function Vector:at_put (idx, val)
+    if idx > self.storage.n then
+        local new_n = self.storage.n
+        while idx > new_n do
+            new_n = new_n * 2
+        end
+
+        local new_storage = alloc_array(new_n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+    self.storage[idx] = val
+
+    if self.last_idx < idx + 1 then
+        self.last_idx = idx + 1
+    end
+end
+
+function Vector:append (elem)
+    if self.last_idx > self.storage.n then
+        -- Need to expand capacity first
+        local new_storage = alloc_array(2 * self.storage.n)
+        for i = 1, self.storage.n do
+            new_storage[i] = self.storage[i]
+        end
+        self.storage = new_storage
+    end
+
+    self.storage[self.last_idx] = elem
+    self.last_idx = self.last_idx + 1
+end
+
+function Vector:is_empty ()
+    return self.last_idx == self.first_idx
+end
+
+function Vector:each (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        fn(self.storage[i])
+    end
+end
+
+function Vector:has_some (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        if fn(self.storage[i]) then
+            return true
+        end
+    end
+    return false
+end
+
+function Vector:get_one (fn)
+    for i = self.first_idx, self.last_idx - 1 do
+        local e = self.storage[i]
+        if fn(e) then
+            return e
+        end
+    end
+    return nil
+end
+
+function Vector:remove_first ()
+    if self:is_empty() then
+        return nil
+    end
+
+    self.first_idx = self.first_idx + 1
+    return self.storage[self.first_idx - 1]
+end
+
+function Vector:remove (obj)
+    local new_array = alloc_array(self:capacity())
+    local new_last = 1
+    local found = false
+
+    self:each(function (it)
+        if it == obj then
+            found = true
+        else
+            new_array[new_last] = it
+            new_last = new_last + 1
+        end
+    end)
+
+    self.storage   = new_array
+    self.last_idx  = new_last
+    self.first_idx = 1
+    return found
+end
+
+function Vector:remove_all ()
+    self.first_idx = 1
+    self.last_idx = 1
+    self.storage = alloc_array(self:capacity())
+end
+
+function Vector:size ()
+    return self.last_idx - self.first_idx
+end
+
+function Vector:capacity ()
+    return self.storage.n
+end
+
+function Vector:sort (fn)
+    -- Make the argument, block, be the criterion for ordering elements of
+    -- the receiver.
+    -- Sort blocks with side effects may not work right.
+    if self:size() > 0 then
+        self:sort_range(self.first_idx, self.last_idx - 1, fn)
+    end
+end
+
+function Vector:sort_range (i, j, fn)
+    assert(fn)
+
+    -- The prefix d means the data at that index.
+    local n = j + 1 - i
+    if n <= 1 then
+        -- Nothing to sort
+        return
+    end
+
+    local storage = self.storage
+    -- Sort di, dj
+    local di = storage[i]
+    local dj = storage[j]
+
+    -- i.e., should di precede dj?
+    if not fn(di, dj) then
+        local tmp = storage[i]
+        storage[i] = storage[j]
+        storage[j] = tmp
+        local tt = di
+        di = dj
+        dj = tt
+    end
+
+    -- NOTE: For DeltaBlue, this is never reached.
+    if n > 2 then               -- More than two elements.
+        local ij  = floor((i + j) / 2)  -- ij is the midpoint of i and j.
+        local dij = storage[ij]         -- Sort di,dij,dj.  Make dij be their median.
+
+        if fn(di, dij) then             -- i.e. should di precede dij?
+            if not fn(dij, dj) then     -- i.e., should dij precede dj?
+               local tmp = storage[j]
+               storage[j] = storage[ij]
+               storage[ij] = tmp
+               dij = dj
+            end
+        else                            -- i.e. di should come after dij
+            local tmp = storage[i]
+            storage[i] = storage[ij]
+            storage[ij] = tmp
+            dij = di
+        end
+
+        if n > 3 then           -- More than three elements.
+            -- Find k>i and l<j such that dk,dij,dl are in reverse order.
+            -- Swap k and l.  Repeat this procedure until k and l pass each other.
+            local k = i
+            local l = j - 1
+
+            while true do
+                -- i.e. while dl succeeds dij
+                while k <= l and fn(dij, storage[l]) do
+                    l = l - 1
+                end
+
+                k = k + 1
+                -- i.e. while dij succeeds dk
+                while k <= l and fn(storage[k], dij) do
+                    k = k + 1
+                end
+
+                if k > l then
+                    break
+                end
+
+                local tmp = storage[k]
+                storage[k] = storage[l]
+                storage[l] = tmp
+            end
+
+            -- Now l < k (either 1 or 2 less), and di through dl are all
+            -- less than or equal to dk through dj.  Sort those two segments.
+            self:sort_range(i, l, fn)
+            self:sort_range(k, j, fn)
+        end
+    end
+end
+
+end -- class Vector
+
+local Set = {_CLASS = 'Set'} do
+
+local INITIAL_SIZE = 10
+
+function Set.new (size)
+    local obj = {
+        items = Vector.new(size or INITIAL_SIZE)
+    }
+    return setmetatable(obj, {__index = Set})
+end
+
+function Set:size ()
+    return self.items:size()
+end
+
+function Set:each (fn)
+    self.items:each(fn)
+end
+
+function Set:has_some (fn)
+    return self.items:has_some(fn)
+end
+
+function Set:get_one (fn)
+    return self.items:get_one(fn)
+end
+
+function Set:add (obj)
+    if not self:contains(obj) then
+        self.items:append(obj)
+    end
+end
+
+function Set:remove_all ()
+    self.items:remove_all()
+end
+
+function Set:collect (fn)
+    local coll = Vector.new()
+    self:each(function (it)
+        coll:append(fn(it))
+    end)
+    return coll
+end
+
+function Set:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class Set
+
+local IdentitySet = {_CLASS = 'IdentitySet'} do
+setmetatable(IdentitySet, {__index = Set})
+
+function IdentitySet.new (size)
+    local obj = Set.new(size)
+    return setmetatable(obj, {__index = IdentitySet})
+end
+
+function IdentitySet:contains (obj)
+    return self:has_some(function (it) return it == obj end)
+end
+
+end -- class IdentitySet
+
+local Entry = {_CLASS = 'Entry'} do
+
+function Entry.new (hash, key, value, next)
+    local obj = {
+        hash  = hash,
+        key   = key,
+        value = value,
+        next  = next,
+    }
+    return setmetatable(obj, {__index = Entry})
+end
+
+function Entry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class Entry
+
+local Dictionary = {_CLASS = 'Dictionary'} do
+
+local INITIAL_CAPACITY = 16
+
+function Dictionary.new (size)
+    local obj = {
+        buckets = alloc_array(size or INITIAL_CAPACITY),
+        size    = 0,
+    }
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function Dictionary:hash (key)
+    if not key then
+        return 0
+    end
+    local hash = key:custom_hash()
+    return bxor(hash, rshift(hash, 16))
+end
+
+function Dictionary:is_empty ()
+    return self.size == 0
+end
+
+function Dictionary:get_bucket_idx (hash)
+    return band(self.buckets.n - 1, hash) + 1
+end
+
+function Dictionary:get_bucket (hash)
+    return self.buckets[self:get_bucket_idx(hash)]
+end
+
+function Dictionary:at (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e:match(hash, key) then
+            return e.value
+        end
+        e = e.next
+    end
+    return nil
+end
+
+function Dictionary:contains_key (key)
+    local hash = self:hash(key)
+    local e = self:get_bucket(hash)
+
+    while e do
+        if e.match(hash, key) then
+            return true
+        end
+        e = e.next
+    end
+    return false
+end
+
+function Dictionary:at_put (key, value)
+    local hash = self:hash(key)
+    local i = self:get_bucket_idx(hash)
+    local current = self.buckets[i]
+
+    if not current then
+        self.buckets[i] = self:new_entry(key, value, hash)
+        self.size = self.size + 1
+    else
+        self:insert_bucket_entry(key, value, hash, current)
+    end
+
+    if self.size > self.buckets.n then
+        self:resize()
+    end
+end
+
+function Dictionary:new_entry (key, value, hash)
+    return Entry.new(hash, key, value, nil)
+end
+
+function Dictionary:insert_bucket_entry (key, value, hash, head)
+    local current = head
+
+    while true do
+        if current:match(hash, key) then
+            current.value = value
+            return
+        end
+        if not current.next then
+            self.size = self.size + 1
+            current.next = self:new_entry(key, value, hash)
+            return
+        end
+        current = current.next
+    end
+end
+
+function Dictionary:resize ()
+    local old_storage = self.buckets
+    self.buckets = alloc_array(old_storage.n * 2)
+    self:transfer_entries(old_storage)
+end
+
+function Dictionary:transfer_entries (old_storage)
+    local buckets = self.buckets
+    for i = 1, old_storage.n do
+        local current = old_storage[i]
+
+        if current then
+            old_storage[i] = nil
+            if not current.next then
+                local hash = band(current.hash, buckets.n - 1) + 1
+                buckets[hash] = current
+            else
+                self:split_bucket(old_storage, i, current)
+            end
+        end
+    end
+end
+
+function Dictionary:split_bucket (old_storage, i, head)
+    local lo_head, lo_tail = nil, nil
+    local hi_head, hi_tail = nil, nil
+    local current = head
+
+    while current do
+        if band(current.hash, old_storage.n) == 0 then
+            if not lo_tail then
+               lo_head = current
+            else
+                lo_tail.next = current
+            end
+            lo_tail = current
+        else
+            if not hi_tail then
+                hi_head = current
+            else
+                hi_tail.next = current
+            end
+            hi_tail = current
+        end
+       current = current.next
+    end
+
+    if lo_tail then
+        lo_tail.next = nil
+        self.buckets[i] = lo_head
+    end
+    if hi_tail then
+        hi_tail.next = nil
+        self.buckets[i + old_storage.n] = hi_head
+    end
+end
+
+function Dictionary:remove_all ()
+    self.buckets = alloc_array(self.buckets.n)
+    self.size = 0
+end
+
+function Dictionary:keys ()
+    local keys = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            keys:append(current.key)
+            current = current.next
+        end
+    end
+    return keys
+end
+
+function Dictionary:values ()
+    local vals = Vector.new(self.size)
+    local buckets = self.buckets
+    for i = 1, buckets.n do
+        local current = buckets[i]
+        while current do
+            vals:append(current.value)
+            current = current.next
+        end
+    end
+    return vals
+end
+
+end -- class Dictionary
+
+local IdEntry = {_CLASS = 'IdEntry'} do
+setmetatable(IdEntry, {__index = Entry})
+
+function IdEntry.new (hash, key, value, next)
+    local obj = Entry.new (hash, key, value, next)
+    return setmetatable(obj, {__index = IdEntry})
+end
+
+function IdEntry:match (hash, key)
+    return self.hash == hash and self.key == key
+end
+
+end -- class IdEntry
+
+local IdentityDictionary = {_CLASS = 'IdentityDictionary'} do
+setmetatable(IdentityDictionary, {__index = Dictionary})
+
+function IdentityDictionary.new (size)
+    local obj = Dictionary.new (size)
+    return setmetatable(obj, {__index = Dictionary})
+end
+
+function IdentityDictionary:new_entry (key, value, hash)
+    return IdEntry.new(hash, key, value, nil)
+end
+
+end -- class IdentityDictionary
+
+local Random = {_CLASS = 'Random'} do
+
+function Random.new ()
+    local obj = {seed = 74755}
+    return setmetatable(obj, {__index = Random})
+end
+
+function Random:next ()
+  self.seed = band(((self.seed * 1309) + 13849), 65535);
+  return self.seed;
+end
+
+end -- class Random
+
+---------------------------------
+
+local benchmark = {} do
+
+function benchmark:inner_benchmark_loop (inner_iterations)
+    for _ = 1, inner_iterations do
+        if not self:verify_result(self:benchmark()) then
+            return false
+        end
+    end
+    return true
+end
+
+function benchmark:benchmark ()
+    error 'subclass_responsibility'
+end
+
+function benchmark:verify_result ()
+    error 'subclass_responsibility'
+end
+
+end
+
+---------------------------------
+
+local towers = {} do
+setmetatable(towers, {__index = benchmark})
+
+local function create_disk (size)
+    return {size = size, next = nil}
+end
+
+function towers:benchmark ()
+    self.piles = {}
+    self:build_tower_at(1, 13)
+    self.moves_done = 0
+    self:move_disks(13, 1, 2)
+    return self.moves_done
+end
+
+function towers:verify_result (result)
+    return 8191 == result
+end
+
+function towers:push_disk (disk, pile)
+    local top = self.piles[pile]
+    if top and disk.size >= top.size then
+      error 'Cannot put a big disk on a smaller one'
+    end
+    disk.next = top
+    self.piles[pile] = disk
+end
+
+function towers:pop_disk_from (pile)
+    local top = self.piles[pile]
+    assert(top, 'Attempting to remove a disk from an empty pile')
+    self.piles[pile] = top.next
+    top.next = nil
+    return top
+end
+
+function towers:move_top_disk (from_pile, to_pile)
+    self:push_disk(self:pop_disk_from(from_pile), to_pile)
+    self.moves_done = self.moves_done + 1
+end
+
+function towers:build_tower_at (pile, disks)
+    for i = disks, 1, -1 do
+        self:push_disk(create_disk(i), pile)
+    end
+end
+
+function towers:move_disks (disks, from_pile, to_pile)
+    if disks == 1 then
+        self:move_top_disk(from_pile, to_pile)
+    else
+        local other_pile = 6 - from_pile - to_pile
+        self:move_disks(disks - 1, from_pile, other_pile)
+        self:move_top_disk(from_pile, to_pile)
+        self:move_disks(disks - 1, other_pile, to_pile)
+    end
+end
+
+end -- object towers
+
+return function(N)
+    towers:inner_benchmark_loop(N)
+end

部分文件因为文件数量过多而无法显示