diff --git a/6502.lua b/6502.lua
index 730509a..e12e50e 100644
--- a/6502.lua
+++ b/6502.lua
@@ -1,5 +1,9 @@
 local M = {}
 
+local symbols = {}
+local sections = {}
+local locations = {}
+
 local byte_normalize = function(v)
     if v < -128 or v > 255 then error("value out of byte range: " .. v) end
     if v < 0 then v = v + 0x100 end
@@ -21,6 +25,126 @@ local word_emit = function(v, bin)
     bin[#bin+1] = 0xff & (v // 0x100)
 end
 
+local link = function()
+    for _,location in ipairs(locations) do
+
+        local chunk_reserve = function(chunk_ix, chunk, start, size)
+            if start == chunk.start then
+                if size == chunk.size then location.chunks[chunk_ix] = nil
+                else chunk.start=start+size chunk.size=chunk.size-size end
+            else
+                if chunk.size - (start - chunk.start) == size then chunk.size = chunk.size - size
+                else
+                    chunk.size = start - chunk.start
+                    table.insert(location.chunks, chunk_ix+1, { start=start+size, size=chunk.start+chunk.size-(start+size) })
+                end
+            end
+        end
+
+        -- filter sections list
+        local position_independent_sections = {}
+        local symbols_to_remove = {}
+        for ix,section in ipairs(sections) do
+            if symbols[section.label] then error("duplicate symbol: " .. section.label) end
+            symbols[section.label] = section
+            section:compute_size()
+            if section.size == 0 then
+                sections[ix]=nil
+                if not section.org then table.insert(symbols_to_remove, section.label) end
+            elseif not section.org then table.insert(position_independent_sections, section) end
+        end
+        for _,v in ipairs(symbols_to_remove) do symbols[v] = nil end
+
+        -- fixed position sections
+        for section_ix,section in ipairs(sections) do if section.org then
+            if section.org < location.start or section.org > location.finish then
+                error("ORG section " .. section.label .. " starts outside container location")
+            end
+            for chunk_ix,chunk in ipairs(location.chunks) do
+                if chunk.start <= section.org and chunk.size - (section.org - chunk.start) >= section.size then
+                    chunk_reserve(chunk_ix, chunk, section.org, section.size)
+                    goto chunk_located
+                end
+            end
+            error("ORG section " .. section.label .. " overflows its location")
+            ::chunk_located::
+        end end
+
+        -- position independent sections
+        table.sort(position_independent_sections, function(a,b) return a.size < b.size end)
+        while #position_independent_sections > 0 do
+            local section = position_independent_sections[1]
+            local chunks = {}
+            for _,chunk in ipairs(location.chunks) do
+                if chunk.size >= section.size then chunks[#chunks+1] = chunk end
+            end
+            table.sort(chunks, function(a,b) return a.size < b.size end)
+            for chunk_ix,chunk in ipairs(chunks) do
+                local waste,position = math.maxinteger
+                local usage_lowest = function(start, finish)
+                    local inc=1
+                    if section.align then
+                        start = (start + section.align - 1) // section.align * section.align
+                        if section.offset then start = start + section.offset end
+                        inc = section.align
+                    end
+                    for address=start,finish,inc do
+                        for _,constraint in ipairs(section.constraints) do
+                            local start, finish = address+contraint.start, address+constraint.finish
+                            if start // 0x100 == finish // 0x100 then
+                                if constraint.type == 'crosspage' then goto constraints_not_met end
+                            else
+                                if constraint.type == 'samepage' then goto constraints_not_met end
+                            end
+                        end
+                        local w = math.min(address - chunk.start, chunk.size - (address - chunk.start))
+                        if w < waste then waste=w position=address end
+                        ::constraints_not_met::
+                    end
+                end
+                local finish = math.min(chunk.start + 0xff, chunk.start + chunk.size - section.size)
+                usage_lowest(chunk.start, finish)
+                if chunk.size ~= math.huge then
+                    local start = math.max(chunk.start + chunk.size - section.size - 0xff, chunk.start)
+                    usage_lowest(start, chunk.start + chunk.size - section.size)
+                end
+                if position then
+                    chunk_reserve(chunk_ix, chunk, position, section.size)
+                    section.org = position
+                    goto chunk_located
+                end
+            end
+            error("unable to find space for section " .. section.label)
+            ::chunk_located::
+        end
+
+    end
+end
+
+M.location = function(start, finish)
+    local size = (finish or math.huge) - start
+    locations[#locations+1] = { start=start, finish=finish, chunks={ { start=start, size=size } } }
+end
+
+M.section = function(t)
+    local section = {}
+    if (type(t) == 'string') then section.label = t
+    else
+        assert(type(t) == 'table')
+        assert(type(t[1]) == 'string' and string.len(t[1]) > 0)
+        section=t section.label=t[1] section[1]=nil
+        if section.offset and not section.align then error("section " .. section.label .. " has offset, but no align") end
+    end
+    section.constraints = {}
+    section.instructions = {}
+    function section:compute_size()
+        self.size = 0
+        for _,instruction in ipairs(self.instructions) do
+            -- TODO
+        end
+    end
+end
+
 M.byte = function(...)
     local data = {...}
     for _,v in ipairs(data) do byte_emit(byte_normalize(v)) end
@@ -30,17 +154,6 @@ M.word = function(...)
     for _,v in ipairs(data) do word_emit(word_normalize(v)) end
 end
 
-M.section = function(t)
-    local s = {}
-    if (type(t) == 'string') then s.name = t
-    else
-        assert(type(t) == 'table')
-        assert(type(t.name) == 'string' and string.len(t.name) > 0)
-        s = t
-    end
-    s.instructions = {}
-end
-
 return M
 
 --[===[