diff --git a/src/terralib.lua b/src/terralib.lua index c41d15e8..14287e19 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2868,7 +2868,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - if reciever:is "var" then + if reciever:is "var" and reciever.type:isstruct() then local mt = reciever.type.metamethods if mt and mt.__init then return checkmethodwithreciever(anchor, true, "__init", reciever, terralib.newlist(), "statement") @@ -2878,7 +2878,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if metamethods.__move is implemented local function checkmove(anchor, reciever) - if reciever:is "var" then + if reciever:is "var" and reciever.type:isstruct() then --check if metamethod __move is implemented local mt = reciever.type.metamethods if mt and mt.__move then @@ -2889,13 +2889,34 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return reciever end + --check if metamethods.__copy is implemented + local function checkcopies(anchor, rhs) + local function checkcopy(reciever) + if reciever:is "var" and reciever.type:isstruct() then + local mt = reciever.type.metamethods + if mt and mt.__copy then + return checkmethodwithreciever(anchor, true, "__copy", reciever, terralib.newlist(), "statement") + end + end + end + --add all implemented copy-assignment methods + local stmts = terralib.newlist{} + for i,r in ipairs(rhs) do + local copy = checkcopy(r) + if copy then + stmts:insert(copy) + end + end + return stmts + end + --check if metamethods.__dtor is implemented local function checkdtors(anchor, lhs) local function checkdtor(reciever) if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - if reciever:is "var" then + if reciever:is "var" and reciever.type:isstruct() then local mt = reciever.type.metamethods if mt and mt.__dtor then return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") @@ -3371,8 +3392,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local res = createassignment(s,lhs,rhs) --destructor calls local dtors = checkdtors(s, lhs) + --copy-assignments calls, which enable side effects in the assignment + local copies = checkcopies(s, rhs) --returned statements: - local stmts = terralib.newlist{ res } + local stmts = terralib.newlist{} + stmts:insertall(copies) + stmts:insert(res) --add deferred calls to the destructors for i,dtor in ipairs(dtors) do stmts:insert(newobject(s, T.defer, dtor)) @@ -3396,15 +3421,18 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local rhs = checkexpressions(s.rhs) local lhs = checkexpressions(s.lhs,"lexpression") local res = createassignment(s,lhs,rhs) - --check for implemented destructor calls + --check for implemented destructor calls and copy-assignments local dtors = checkdtors(s, lhs) + local copies = checkcopies(s, rhs) --returned statements: local stmts = terralib.newlist() --(1) first apply destructor calls to free any heap memory of 'lhs' objects stmts:insertall(dtors) - --(2) actual assignment + --(2) apply copy-constructor calls, which enable side effects in the assignment + stmts:insertall(copies) + --(3) actual assignment stmts:insert(res) - --(3) add deferred calls to the destructors + --(4) add deferred calls to the destructors for i,dtor in ipairs(dtors) do stmts:insert(newobject(s, T.defer, dtor)) end diff --git a/tests/smartptr.t b/tests/smartptr.t new file mode 100644 index 00000000..eb8457ad --- /dev/null +++ b/tests/smartptr.t @@ -0,0 +1,136 @@ +local function printtestdescription(s) + print() + print("======================================") + print(s) + print("======================================") +end + +local std = {} +std.io = terralib.includec("stdio.h") +std.lib = terralib.includec("stdlib.h") + +--implementation of a smart (shared) pointer type +struct A{ + data : &int --underlying data ptr +} + +A.methods.refcounter = terra(self : &A) + if self.data ~= nil then + return [&int8](self.data+1) + end + return nil +end + +A.methods.increaserefcounter = terra(self : &A) + var ptr = self:refcounter() + if ptr ~= nil then + @ptr = @ptr+1 + end +end + +A.methods.decreaserefcounter = terra(self : &A) + var ptr = self:refcounter() + if ptr ~= nil then + @ptr = @ptr-1 + end +end + +--initialization of pointer variables +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: initializing object. start.\n") + self.data = nil -- initialize data pointer to nil + std.io.printf("__init: initializing object. return.\n") +end + +--move-assignment operation +A.metamethods.__move = terra(self : &A) + std.io.printf("__move: moving object. start.\n") + defer std.io.printf("__move: moving object. return.\n") + var tmp : A + tmp.data = self.data --moving data to temporary variable + self.data = nil --setting data of self to nil, which makes it safe to delete + return tmp +end + +--destructor +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor. start\n") + defer std.io.printf("__dtor: calling destructor. return\n") + --if uninitialized then do nothing + if self.data == nil then + return + end + --the reference counter is `nil`, `1` or `> 1`. + --free memory if the last shared pointer obj runs out of life + if @self:refcounter() == 1 then + std.io.printf("__dtor: reference counter: %d -> %d.\n", @self:refcounter(), @self:refcounter()-1) + std.io.printf("__dtor: free'ing memory.\n") + std.lib.free(self.data) + self.data = nil --reinitialize data ptr + --otherwise reduce reference counter + else + self:decreaserefcounter() --decrease the reference counter + std.io.printf("__dtor: reference counter: %d -> %d.\n", @self:refcounter()+1, @self:refcounter()) + end +end + +--copy-assignment operation +--chosen to operate only on self, which is flexible enough to implement the behavior of +--a shared smart pointer type +A.metamethods.__copy = terra(self : &A) + std.io.printf("__copy: calling copy-assignment operator. start\n") + defer std.io.printf("__copy: calling copy-assignment operator. return\n") + self:increaserefcounter() + return self +end + +local alloc = terra() + std.io.printf("alloc: allocating memory. start\n") + defer std.io.printf("alloc: allocating memory. return.\n") + var x : A + --heap allocation for `data` with the reference counter `refcount` stored in + --its tail + var head = sizeof(int) + var tail = sizeof(int8) + x.data = [&int](std.lib.malloc(head+tail)) + --initializing the reference counter to one + @x.data = 10 + @x:refcounter() = 1 + return x +end + +--testing vardef and copy assign +local terra test0() + var a : A + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = alloc() + std.io.printf("main: a.data: %d\n", @a.data) + std.io.printf("main: a.refcount: %d\n", @a:refcounter()) + var b = a + std.io.printf("main: b.data: %d\n", @b.data) + std.io.printf("main: a.refcount: %d\n", @a:refcounter()) + std.io.printf("main: b.refcount: %d\n", @b:refcounter()) + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + std.io.printf("main: b.refcount: %p\n", b:refcounter()) +end + +--testing var and copy assign +local terra test1() + var a : A, b : A + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = alloc() + std.io.printf("main: a.data: %d\n", @a.data) + std.io.printf("main: a.refcount: %d\n", @a:refcounter()) + b = a + std.io.printf("main: b.data: %d\n", @b.data) + std.io.printf("main: a.refcount: %d\n", @a:refcounter()) + std.io.printf("main: b.refcount: %d\n", @b:refcounter()) + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + std.io.printf("main: b.refcount: %p\n", b:refcounter()) +end + +printtestdescription("smartptr - vardef assignment.") +test0() + +printtestdescription("smartptr - copy assignment.") +test1() \ No newline at end of file