Skip to content

Commit

Permalink
<feature> raii - added copy-assignment metamethod.
Browse files Browse the repository at this point in the history
  • Loading branch information
hiemstar committed May 11, 2024
1 parent cbeba09 commit c964286
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 7 deletions.
42 changes: 35 additions & 7 deletions src/terralib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down
136 changes: 136 additions & 0 deletions tests/smartptr.t
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit c964286

Please sign in to comment.