{ $Id: $ This file is part of the Free Pascal run time library. Copyright (c) 2004 by Daniel Mantione member of the Free Pascal development team Implements a memory manager that makes use of the fact that a program is running in a virtual address space where pages can be allocated at random, instead of a more traditional growing heap. See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit pagemem; {*****************************************************************************} interface {*****************************************************************************} {*****************************************************************************} implementation {*****************************************************************************} {$packrecords 1} {$packenum 1} type Tpage_type=(pt_8byte_with_bitmap,pt_suballocation,pt_direct_page); Ppage_type=^Tpage_type; Pcriterium=^Tcriterium; Tcriterium=record criterium1,criterium2:cardinal; end; Ptree_struct=^Ttree_struct; Ttree_struct=record left,right:ptruint; end; {This page layout is targeted at very short strings and linked lists with very low payload. It uses fixed memory sizes of 8 byte. Memory overhead should be avoided at all here. An allocation bitmap does this very well, only 1 bit per memory block.} Ppage_8byte_with_bitmap=^Tpage_8byte_with_bitmap; Tpage_8byte_with_bitmap=record page_type:Tpage_type; search_index:byte; free_count:word; page_birthyear:cardinal; freelist_prev,freelist_next:Ppage_8byte_with_bitmap; block_allocation_map:array[0..15] of cardinal; end; Ppage_suballocation=^Tpage_suballocation; Tpage_suballocation=record page_type:Tpage_type; reserved:array[1..3] of byte; page_birthyear:cardinal; end; {This page layout is targeted at large memory blocks. We allocate pages directly from the OS for such blocks.} Ppage_direct=^Tpage_direct; Tpage_direct=record page_type:Tpage_type; reserved:array[1..3] of byte; size:cardinal; end; Pfree_block=^Tfree_block; Tfree_block=record size:cardinal; tree_sizememloc:Ttree_struct; tree_memlocation:Ttree_struct; end; Tsplay_status=(ts_not_found,ts_found_on_left, ts_found_on_p,ts_found_on_right); Psuballoc_header=^Tsuballoc_header; Tsuballoc_header=record alloc_size:ptruint; end; const tree_sizememloc_offset=4; tree_memlocation_offset=12; page_size=4096; page_shift=12; page_mask=$00000fff; page_8byte_with_bitmap_maxspace= (page_size-sizeof(Tpage_8byte_with_bitmap)) div 8; memblock_align=4; memblock_alignround=memblock_align-1; min_suballoc_size=sizeof(Tfree_block); const freelist_8byte_with_bitmap:Ppage_8byte_with_bitmap=nil; page_8byte_with_bitmap_init:Tpage_8byte_with_bitmap= ( page_type:pt_8byte_with_bitmap; search_index:0; free_count:page_8byte_with_bitmap_maxspace; page_birthyear:0; freelist_prev:nil; freelist_next:nil; block_allocation_map:($ffffffff,$ffffffff,$ffffffff,$ffffffff, $ffffffff,$ffffffff,$ffffffff,$ffffffff, $ffffffff,$ffffffff,$ffffffff,$ffffffff, $ffffffff,$ffffffff,$ffffffff,$ffffffff) ); var tree_sizememloc,tree_memlocation:Pfree_block; {**************************************************************************** Page allocation/deallocation ****************************************************************************} function fpmmap(adr:pointer;len,prot,flags,fd,off:sizeint):pointer;external name 'FPC_SYSC_MMAP'; function fpmunmap(adr:pointer;len:sizeint):pointer;external name 'FPC_SYSC_MUNMAP'; function geterrno:longint;external name 'FPC_SYS_GETERRNO'; const PROT_READ = $1; { page can be read } PROT_WRITE = $2; { page can be written } PROT_EXEC = $4; { page can be executed } PROT_NONE = $0; { page can not be accessed } MAP_SHARED = $1; { Share changes } MAP_PRIVATE = $2; { Changes are private } MAP_TYPE = $f; { Mask for type of mapping } MAP_FIXED = $10; { Interpret addr exactly } MAP_ANONYMOUS = $20; { don't use a file } MAP_GROWSDOWN = $100; { stack-like segment } MAP_DENYWRITE = $800; { ETXTBSY } MAP_EXECUTABLE = $1000; { mark it as an executable } MAP_LOCKED = $2000; { pages are locked } MAP_NORESERVE = $4000; { don't check for reservations } function req_pages(count:cardinal):pointer; {Requests count consecutive pages from the OS.} begin req_pages:=fpmmap(nil,count shl page_shift,PROT_READ or PROT_WRITE, MAP_PRIVATE or MAP_ANONYMOUS,0,0); if geterrno<>0 then req_pages:=nil; {This one can fail, so we can handle an out of memory situation.} end; procedure sack_pages(p:pointer;count:cardinal); begin fpmunmap(p,count shl page_shift); if geterrno<>0 then runerror(204); {This one should succees.} end; {**************************************************************************** 8-bit bitmap allocated memory ****************************************************************************} procedure new_page_8byte_with_bitmap; var page:Ppage_8byte_with_bitmap; begin page:=req_pages(1); page^:=page_8byte_with_bitmap_init; page^.freelist_next:=freelist_8byte_with_bitmap; page^.freelist_prev:=nil; if freelist_8byte_with_bitmap<>nil then freelist_8byte_with_bitmap^.freelist_prev:=page; freelist_8byte_with_bitmap:=page; end; function pgetmem_8byte_with_bitmap:pointer; var page:Ppage_8byte_with_bitmap; bit:cardinal; begin if freelist_8byte_with_bitmap=nil then new_page_8byte_with_bitmap; page:=freelist_8byte_with_bitmap; with page^ do begin {Search a dword in which a bit is set.} while block_allocation_map[search_index]=0 do search_index:=(search_index+1) and 15; ptrint(pgetmem_8byte_with_bitmap):=ptrint(page)+sizeof(page^)+search_index*256; {Search for a set bit in the dword.} bit:=1; while block_allocation_map[search_index] and bit=0 do begin bit:=bit shl 1; inc(ptrint(pgetmem_8byte_with_bitmap),8); end; {Allocate the block.} block_allocation_map[search_index]:=block_allocation_map[search_index] and not bit; dec(free_count); if free_count=0 then begin {There is no space left in this page. Remove it from the freelist.} if freelist_next<>nil then freelist_next^.freelist_prev:=freelist_prev; if freelist_prev<>nil then freelist_prev^.freelist_next:=freelist_next; if freelist_8byte_with_bitmap=page then freelist_8byte_with_bitmap:=freelist_next; freelist_prev:=nil; freelist_next:=nil; end; end; end; function pfreemem_8byte_with_bitmap(page:Ppage_8byte_with_bitmap;p:pointer):ptrint; var index,bit:cardinal; begin index:=(ptrint(p)-ptrint(page)-sizeof(page^)) div 8; bit:=index and 31; index:=index shr 5; with page^ do begin if free_count=0 then begin {Page will get free slots. Must be included in freelist.} if freelist_8byte_with_bitmap=nil then freelist_8byte_with_bitmap:=page else begin freelist_next:=freelist_8byte_with_bitmap; freelist_8byte_with_bitmap^.freelist_prev:=page; freelist_8byte_with_bitmap:=page; end; {Make sure the next allocation finds the slot without much searching.} search_index:=index; end; block_allocation_map[index]:=block_allocation_map[index] or (1 shl bit); inc(free_count); if free_count=page_8byte_with_bitmap_maxspace then begin {The page is completely free. It can be returned to the OS, but remove it from the freelist first.} if freelist_next<>nil then freelist_next^.freelist_prev:=freelist_prev; if freelist_prev<>nil then freelist_prev^.freelist_next:=freelist_next; if freelist_8byte_with_bitmap=page then freelist_8byte_with_bitmap:=freelist_next; sack_pages(page,1); end; end; pfreemem_8byte_with_bitmap:=8; end; {**************************************************************************** Splay tree stuff ****************************************************************************} { $define debug} {$ifdef debug} procedure write_sizememloc_tree(tree:Pfree_block;level:cardinal); var i:cardinal; begin if tree=nil then exit; write_sizememloc_tree(Pfree_block(tree^.tree_sizememloc.left),level+1); for i:=1 to level do write(' '); writeln(tree^.size,' ',hexstr(ptruint(tree),8)); write_sizememloc_tree(Pfree_block(tree^.tree_sizememloc.right),level+1); end; procedure write_memlocation_tree(tree:Pfree_block;level:cardinal); var i:cardinal; begin if tree=nil then exit; write_memlocation_tree(Pfree_block(tree^.tree_memlocation.left),level+1); for i:=1 to level do write(' '); writeln(hexstr(ptruint(tree),8)); write_memlocation_tree(Pfree_block(tree^.tree_memlocation.right),level+1); end; {$endif} procedure rotate_l(var p:ptruint;offset:cardinal); var p1:ptruint; begin p1:=Ptree_struct(p+offset)^.right; Ptree_struct(p+offset)^.right:=Ptree_struct(p1+offset)^.left; Ptree_struct(p1+offset)^.left:=p; p:=p1; end; procedure rotate_r(var p:ptruint;offset:cardinal); var p1:ptruint; begin p1:=Ptree_struct(p+offset)^.left; Ptree_struct(p+offset)^.left:=Ptree_struct(p1+offset)^.right; Ptree_struct(p1+offset)^.right:=p; p:=p1; end; procedure zigzig(var p:ptruint;offset:cardinal);inline; begin rotate_r(p,offset); rotate_r(p,offset); end; procedure zigzag(var p:ptruint;offset:cardinal);inline; begin rotate_l(Ptree_struct(p+offset)^.left,offset); rotate_r(p,offset); end; procedure zagzig(var p:ptruint;offset:cardinal);inline; begin rotate_r(Ptree_struct(p+offset)^.right,offset); rotate_l(p,offset); end; procedure zagzag(var p:ptruint;offset:cardinal);inline; begin rotate_l(p,offset); rotate_l(p,offset); end; procedure delete_from_tree(var p:ptruint;offset:cardinal); var p1:ptruint; pp1:^ptruint; begin if Ptree_struct(p+offset)^.left=0 then p:=Ptree_struct(p+offset)^.right else begin if Ptree_struct(p+offset)^.right<>0 then begin {Both are occupied. Move right to rightmost leaf of left.} p1:=Ptree_struct(p+offset)^.left; repeat pp1:=@Ptree_struct(p1+offset)^.right; p1:=pp1^; until p1=0; pp1^:=Ptree_struct(p+offset)^.right; end; p:=Ptree_struct(p+offset)^.left; end; end; function find_sizememloc(size:ptruint;var p:Pfree_block):Tsplay_status; begin find_sizememloc:=ts_found_on_p; if p=nil then find_sizememloc:=ts_not_found else if size
p^.size then
case find_sizememloc(size,Pfree_block(p^.tree_sizememloc.right)) of
ts_not_found:
if p^.size nil then
delete_from_tree(ptruint(p^),tree_sizememloc_offset);
end;
function get_block_by_size(size:cardinal):Pfree_block;
var what:^ptruint;
begin
case find_sizememloc(size,tree_sizememloc) of
ts_not_found:
begin
get_block_by_size:=nil;
exit;
end;
ts_found_on_left:
what:=@tree_sizememloc^.tree_sizememloc.left;
ts_found_on_p:
what:=@tree_sizememloc;
ts_found_on_right:
what:=@tree_sizememloc^.tree_sizememloc.right;
end;
get_block_by_size:=Pfree_block(what^);
delete_from_tree(what^,tree_sizememloc_offset);
if get_memlocation(get_block_by_size)=nil then
runerror(204);
end;
function get_block_by_memlocation(location:ptruint):Pfree_block;
var what:^ptruint;
begin
get_block_by_memlocation:=get_memlocation(Pfree_block(location));
if get_block_by_memlocation<>nil then
begin
get_sizememloc(get_block_by_memlocation);
{ case find_sizememloc(get_block_by_memlocation^.size,
ptruint(get_block_by_memlocation),tree_sizememloc) of
ts_not_found:
runerror(204);
ts_found_on_left:
what:=@tree_sizememloc^.tree_sizememloc.left;
ts_found_on_p:
what:=@tree_sizememloc;
ts_found_on_right:
what:=@tree_sizememloc^.tree_sizememloc.right;
end;
delete_from_tree(what^,tree_sizememloc_offset);}
end;
end;
function get_smaller_neighbour(location:ptruint):Pfree_block;
var p,what:^ptruint;
begin
{Find a smaller block. Don't splay as it will be deleted.}
p:=@tree_memlocation;
what:=nil;
while (p^<>0) do
if location<=p^ then
p:=@Pfree_block(p^)^.tree_memlocation.left
else
begin
what:=p;
p:=@Pfree_block(p^)^.tree_memlocation.right;
end;
if (what=nil) or (ptruint(what^)+Pfree_block(what^)^.size<>location) then
begin
get_smaller_neighbour:=nil;
exit;
end;
get_smaller_neighbour:=Pfree_block(what^);
delete_from_tree(ptruint(what^),tree_memlocation_offset);
get_sizememloc(get_smaller_neighbour);
end;
{function pgetmem_directpage(memsize:ptrint):pointer;
var npages:ptrint;
begin
npages:=(memsize+sizeof(Tpage_direct)+page_size-1) div page_size;
pgetmem_directpage:=req_pages(npages);
with Ppage_direct(pgetmem_directpage)^ do
begin
page_type:=pt_direct_page;
size:=memsize;
end;
end;
}
function pgetmem_suballocpage(memsize:ptrint):pointer;
var free_block:Pfree_block;
page:pointer;
needsize,remaining,block_start:ptruint;
begin
{$ifdef debug}
writeln('-------Getmem------- ',memsize);
{$endif}
{Constant parts on left because of constant evaluation.}
needsize:=(sizeof(Tsuballoc_header)+memblock_alignround+memsize) and not memblock_alignround;
if needsize