From e248051d3ce6c84dd5eb184a845e0f2ec97ae65f Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:24:26 +0200 Subject: [PATCH 01/69] added test artifacts to .ignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index d3d42d9f3..3bd7b8ebc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /build +/cmake-build-debug /docs/_site /docs/_vendor /install @@ -18,6 +19,13 @@ /tests/dynlib /tests/dynlib.exe /tests/not_bc +/tests/class2 +/tests/inline_c.exe +/tests/objc +/tests/objc2 +/tests/stdio.exe + +/.idea *.bc *.ll From 0e2cdb055f62509bdfe22559863feeeaa8fc02c6 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:31:22 +0200 Subject: [PATCH 02/69] added __init metamethod to initialize managed types in raii. --- src/terralib.lua | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index cc6b2d60a..16dda343c 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2762,6 +2762,29 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkcall(anchor, terra.newlist { fnlike }, fnargs, "first", false, location) end + --check if metamethods.__init is implemented + local function checkmetainit(anchor, reciever) + if reciever.type and reciever.type:isstruct() then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + end + if reciever.type.metamethods.__init then + return checkmethodwithreciever(anchor, true, "__init", reciever, terralib.newlist(), "statement") + end + end + end + + local function checkmetainitializers(anchor, lhs) + local stmts = terralib.newlist() + for i,e in ipairs(lhs) do + local init = checkmetainit(anchor, e) + if init then + stmts:insert(init) + end + end + return stmts + end + local function checkmethod(exp, location) local methodname = checklabel(exp.name,true).value assert(type(methodname) == "string" or terra.islabel(methodname)) @@ -3309,9 +3332,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif s:is "defvar" then local rhs = s.hasinit and checkexpressions(s.initializers) local lhs = checkformalparameterlist(s.variables, not s.hasinit) - local res = s.hasinit and createassignment(s,lhs,rhs) - or createstatementlist(s,lhs) - return res + if s.hasinit then + return createassignment(s,lhs,rhs) + else + local res = createstatementlist(s,lhs) + res.statements:insertall(checkmetainitializers(s, lhs)) + return res + end elseif s:is "assignment" then local rhs = checkexpressions(s.rhs) local lhs = checkexpressions(s.lhs,"lexpression") From 683526dcb5003e9c5bdd48f260fd42d830a25cd4 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:32:07 +0200 Subject: [PATCH 03/69] added __dtor metamethod to destruct managed variables in raii. --- src/terralib.lua | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 16dda343c..7fb4320f4 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3247,9 +3247,59 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.assignment,lhs,rhs) end + local function checkmetadtors(anchor, stats) + --extract the return statement from `stats`, if there is one + local function extractreturnstat() + local n = #stats + if n>0 then + local s = stats[n] + if s:is "returnstat" then + return s + end + end + end + local rstat = extractreturnstat() + --extract the returned `var` symbols from a return statement + local function extractreturnedsymbols() + local ret = {} + --loop over expressions in a `letin` return statement + for i,v in ipairs(rstat.expression.expressions) do + if v:is "var" then + ret[v.name] = v.symbol + end + end + return ret + end + --check if a __dtor metamethod is implemented for the type corresponding to `sym` + local function checkdtor(name,sym) + local mt = sym.type.metamethods + if mt and mt.__dtor then + local reciever = newobject(anchor, T.var, name, sym):setlvalue(true):withtype(sym.type) + return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + end + end + + --get symbols that are returned in case of a return statement + local rsyms = rstat and extractreturnedsymbols() or {} + --get position at which to add destructor statements + local pos = rstat and #stats or #stats+1 + for name,sym in pairs(env:localenv()) do + --if not a return variable ckeck for an implementation of metamethods.__dtor + if not rsyms[name] then + local dtor = checkdtor(name,sym) + if dtor then + --add deferred calls to the destructors + table.insert(stats, pos, newobject(anchor, T.defer, dtor)) + pos = pos + 1 + end + end + end + return stats + end + function checkblock(s) env:enterblock() - local stats = checkstmts(s.statements) + local stats = checkmetadtors(s, checkstmts(s.statements)) env:leaveblock() return s:copy {statements = stats} end From 5ba46c86c3bff50b9275e0e6215184ba9101e321 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:40:51 +0200 Subject: [PATCH 04/69] added __copy metamethod which enables specialized copy-assignment and construction. --- src/terralib.lua | 129 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 11 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7fb4320f4..d2e1d6f6b 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2785,6 +2785,39 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stmts end + local function hasmetacopyassignment(typ) + if typ and typ:isstruct() and typ.metamethods.__copy then + return true + end + return false + end + + local function checkmetacopyassignment(anchor, from, to) + --if neither `from` or `to` are a struct then return + if not (hasmetacopyassignment(from.type) or hasmetacopyassignment(to.type)) then + return + end + --if `to` is an allocvar then set type and turn into corresponding `var` + if to:is "allocvar" then + local typ = from.type or terra.types.error + to:settype(typ) + to = newobject(anchor,T.var,to.name,to.symbol):setlvalue(true):withtype(to.type) + end + --list of overloaded __copy metamethods + local overloads = terra.newlist() + local function checkoverload(v) + if hasmetacopyassignment(v.type) then + overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) + end + end + --add overloaded methods based on left- and right-hand-side of the assignment + checkoverload(from) + checkoverload(to) + if #overloads > 0 then + return checkcall(anchor, overloads, terralib.newlist{from, to}, "all", true, "expression") + end + end + local function checkmethod(exp, location) local methodname = checklabel(exp.name,true).value assert(type(methodname) == "string" or terra.islabel(methodname)) @@ -3211,7 +3244,28 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.letin, stmts, List {}, true):withtype(terra.types.unit) end + --divide assignment into regular assignments and copy assignments + local function assignmentkinds(lhs, rhs) + local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} + local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} + for i=1,#lhs do + local rhstype = rhs[i] and rhs[i].type + local lhstype = lhs[i].type + if rhstype and (hasmetacopyassignment(lhstype) or hasmetacopyassignment(rhstype)) then + --add assignment by __copy call + byfcall.lhs:insert(lhs[i]) + byfcall.rhs:insert(rhs[i]) + else + --default to regular assignment + regular.lhs:insert(lhs[i]) + regular.rhs:insert(rhs[i]) + end + end + return regular, byfcall + end + local function createassignment(anchor,lhs,rhs) + --special case where a rhs struct is unpacked if #lhs > #rhs and #rhs > 0 then local last = rhs[#rhs] if last.type:isstruct() and last.type.convertible == "tuple" and #last.type.entries + #rhs - 1 == #lhs then @@ -3231,20 +3285,73 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return createstatementlist(anchor, List {a1, a2}) end end - local vtypes = lhs:map(function(v) return v.type or "passthrough" end) - rhs = insertcasts(anchor,vtypes,rhs) - for i,v in ipairs(lhs) do - local rhstype = rhs[i] and rhs[i].type or terra.types.error - if v:is "setteru" then - local rv,r = allocvar(v,rhstype,"") - lhs[i] = newobject(v,T.setter, rv,v.setter(r)) - elseif v:is "allocvar" then - v:settype(rhstype) + + if #lhs < #rhs then + --an error may be reported later during type-checking: 'expected #lhs parameters (...), but found #rhs (...)' + local vtypes = lhs:map(function(v) return v.type or "passthrough" end) + rhs = insertcasts(anchor, vtypes, rhs) + for i,v in ipairs(lhs) do + local rhstype = rhs[i] and rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + lhs[i] = newobject(v,T.setter, rv,v.setter(r)) + elseif v:is "allocvar" then + v:settype(rhstype) + else + ensurelvalue(v) + end + end + return newobject(anchor,T.assignment,lhs,rhs) + else + --standard case #lhs == #rhs + --first take care of regular assignments + local regular, byfcall = assignmentkinds(lhs, rhs) + local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) + regular.rhs = insertcasts(anchor, vtypes, regular.rhs) + for i,v in ipairs(regular.lhs) do + local rhstype = regular.rhs[i] and regular.rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + regular.lhs[i] = newobject(v,T.setter, rv,v.setter(r)) + elseif v:is "allocvar" then + v:settype(rhstype) + else + ensurelvalue(v) + end + end + --take care of copy assignments using metamethods.__copy + local stmts = terralib.newlist() + for i,v in ipairs(byfcall.lhs) do + local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], r)) + stmts:insert(newobject(v,T.setter, rv, v.setter(r))) + elseif v:is "allocvar" then + v:settype(rhstype) + stmts:insert(v) + local init = checkmetainit(anchor, v) + if init then + stmts:insert(init) + end + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + else + ensurelvalue(v) + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + end + end + if #stmts==0 then + --standard case, no meta-copy-assignments + return newobject(anchor,T.assignment, regular.lhs, regular.rhs) else - ensurelvalue(v) + --managed case using meta-copy-assignments + --the calls to `__copy` are in `stmts` + if #regular.lhs>0 then + stmts:insert(newobject(anchor,T.assignment, regular.lhs, regular.rhs)) + end + return createstatementlist(anchor, stmts) end end - return newobject(anchor,T.assignment,lhs,rhs) end local function checkmetadtors(anchor, stats) From d867d2816ccf2b3dccc8ef6d9bec3942856287a5 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:43:26 +0200 Subject: [PATCH 05/69] added simple testing of raii metamethods __init, __dtor, __copy, and a simple implementation of a unique smart pointer type with move semantics. --- tests/raii-unique_ptr.t | 95 +++++++++++++++++++++++++++++++++++ tests/raii.t | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 tests/raii-unique_ptr.t create mode 100644 tests/raii.t diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t new file mode 100644 index 000000000..4d41257ef --- /dev/null +++ b/tests/raii-unique_ptr.t @@ -0,0 +1,95 @@ +local std = {} +std.io = terralib.includec("stdio.h") +std.lib = terralib.includec("stdlib.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : &int + heap : bool +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: initializing object. start.\n") + self.data = nil -- initialize data pointer to nil + self.heap = false --flag to denote heap resource + std.io.printf("__init: initializing object. return.\n") +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor. start\n") + defer std.io.printf("__dtor: calling destructor. return\n") + if self.heap then + std.lib.free(self.data) + self.data = nil + self.heap = false + std.io.printf("__dtor: freed memory.\n") + end +end + +A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.metamethods.__copy:adddefinition( +terra(from : &A, to : &A) + std.io.printf("__copy: moving resources {&A, &A} -> {}.\n") + to.data = from.data + to.heap = from.heap + from.data = nil + from.heap = false +end) +A.metamethods.__copy:adddefinition( +terra(from : &int, to : &A) + std.io.printf("__copy: assignment {&int, &A} -> {}.\n") + to.data = from + to.heap = false --not known at compile time +end) + +--dereference ptr +terra A.methods.getvalue(self : &A) + return @self.data +end + +terra A.methods.setvalue(self : &A, value : int) + @self.data = value +end + +--heap memory allocation +terra A.methods.allocate(self : &A) + std.io.printf("allocate: allocating memory. start\n") + defer std.io.printf("allocate: allocating memory. return.\n") + self.data = [&int](std.lib.malloc(sizeof(int))) + self.heap = true +end + +terra testdereference() + var ptr : A + ptr:allocate() + ptr:setvalue(3) + return ptr:getvalue() +end + +terra returnheapresource() + var ptr : A + ptr:allocate() + ptr:setvalue(3) + return ptr +end + +terra testgetptr() + var ptr = returnheapresource() + return ptr:getvalue() +end + +local test = require "test" + +printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") +test.eq(testdereference(), 3) + +printtestheader("raii-unique_ptr.t: test return heap resource from function") +test.eq(testgetptr(), 3) + + diff --git a/tests/raii.t b/tests/raii.t new file mode 100644 index 000000000..4482f3df3 --- /dev/null +++ b/tests/raii.t @@ -0,0 +1,106 @@ +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = from.data + 10 +end) +A.metamethods.__copy:adddefinition(terra(from : int, to : &A) + std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") + to.data = from +end) +A.metamethods.__copy:adddefinition(terra(from : &A, to : &int) + std.io.printf("__copy: calling copy assignment {&A, &int} -> {}.\n") + @to = from.data +end) + + +terra testinit() + var a : A + return a.data +end + +terra testdtor() + var x : &int + do + var a : A + x = &a.data + end + return @x +end + +terra testcopyconstruction() + var a : A + var b = a + return b.data +end + +terra testcopyassignment() + var a : A + a.data = 2 + var b : A + b = a + return b.data +end + +terra testcopyassignment1() + var a : A + a = 3 + return a.data +end + +terra testcopyassignment2() + var a : A + var x : int + a.data = 5 + x = a + return x +end + +local test = require "test" + +--test if __init is called on object initialization to set 'a.data = 1' +printtestheader("raii.t - testing __init metamethod") +test.eq(testinit(), 1) + +--test if __dtor is called at the end of the scope to set 'a.data = -1' +printtestheader("raii.t - testing __dtor metamethod") +test.eq(testdtor(), -1) + +--test if __copy is called in construction 'var b = a' +printtestheader("raii.t - testing __copy metamethod in copy-construction") +test.eq(testcopyconstruction(), 11) + +--test if __copy is called in an assignment 'b = a' +printtestheader("raii.t - testing __copy metamethod in copy-assignment") +test.eq(testcopyassignment(), 12) + +--test if __copy is called in an assignment 'a = 3' +printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") +test.eq(testcopyassignment1(), 3) + +--test if __copy is called in an assignment 'x = a' for integer x +printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") +test.eq(testcopyassignment2(), 5) \ No newline at end of file From 52ae95db2fe8c01156baac55d0f96493346f9582 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:45:59 +0200 Subject: [PATCH 06/69] revert commented out code to make things work on my local machine. --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index d2e1d6f6b..9f1949573 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3692,11 +3692,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 31482a860729505c0a193c85365a54568281fee6 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 23:30:37 +0200 Subject: [PATCH 07/69] now calling __dtor before a new copy-assignment --- src/terralib.lua | 119 +++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 9f1949573..7e33095b7 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2785,6 +2785,61 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stmts end + --check if a __dtor metamethod is implemented for the type corresponding to `sym` + local function checkmetadtor(anchor, reciever) + if reciever.type and reciever.type:isstruct() then + if reciever.type.metamethods.__dtor then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + end + return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + end + end + end + + local function checkmetadtors(anchor, stats) + --extract the return statement from `stats`, if there is one + local function extractreturnstat() + local n = #stats + if n>0 then + local s = stats[n] + if s:is "returnstat" then + return s + end + end + end + local rstat = extractreturnstat() + --extract the returned `var` symbols from a return statement + local function extractreturnedsymbols() + local ret = {} + --loop over expressions in a `letin` return statement + for i,v in ipairs(rstat.expression.expressions) do + if v:is "var" then + ret[v.name] = v.symbol + end + end + return ret + end + + --get symbols that are returned in case of a return statement + local rsyms = rstat and extractreturnedsymbols() or {} + --get position at which to add destructor statements + local pos = rstat and #stats or #stats+1 + for name,sym in pairs(env:localenv()) do + --if not a return variable ckeck for an implementation of metamethods.__dtor + if not rsyms[name] then + local reciever = newobject(anchor,T.var, name, sym):setlvalue(true):withtype(sym.type) + local dtor = checkmetadtor(anchor, reciever) + if dtor then + --add deferred calls to the destructors + table.insert(stats, pos, newobject(anchor, T.defer, dtor)) + pos = pos + 1 + end + end + end + return stats + end + local function hasmetacopyassignment(typ) if typ and typ:isstruct() and typ.metamethods.__copy then return true @@ -3337,6 +3392,10 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) + local dtor = checkmetadtor(anchor, v) + if dtor then + stmts:insert(dtor) + end stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end end @@ -3354,56 +3413,6 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end - local function checkmetadtors(anchor, stats) - --extract the return statement from `stats`, if there is one - local function extractreturnstat() - local n = #stats - if n>0 then - local s = stats[n] - if s:is "returnstat" then - return s - end - end - end - local rstat = extractreturnstat() - --extract the returned `var` symbols from a return statement - local function extractreturnedsymbols() - local ret = {} - --loop over expressions in a `letin` return statement - for i,v in ipairs(rstat.expression.expressions) do - if v:is "var" then - ret[v.name] = v.symbol - end - end - return ret - end - --check if a __dtor metamethod is implemented for the type corresponding to `sym` - local function checkdtor(name,sym) - local mt = sym.type.metamethods - if mt and mt.__dtor then - local reciever = newobject(anchor, T.var, name, sym):setlvalue(true):withtype(sym.type) - return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") - end - end - - --get symbols that are returned in case of a return statement - local rsyms = rstat and extractreturnedsymbols() or {} - --get position at which to add destructor statements - local pos = rstat and #stats or #stats+1 - for name,sym in pairs(env:localenv()) do - --if not a return variable ckeck for an implementation of metamethods.__dtor - if not rsyms[name] then - local dtor = checkdtor(name,sym) - if dtor then - --add deferred calls to the destructors - table.insert(stats, pos, newobject(anchor, T.defer, dtor)) - pos = pos + 1 - end - end - end - return stats - end - function checkblock(s) env:enterblock() local stats = checkmetadtors(s, checkstmts(s.statements)) @@ -3692,11 +3701,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From a68574520e613702b9aea98599a29a85a89071bd Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 21 May 2024 14:13:27 +0200 Subject: [PATCH 08/69] fixed dispatch of __copy. calling __copy is limited to rhs being a 'var', 'literal' or 'constant'. --- src/terralib.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7e33095b7..fdae97bdc 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2840,8 +2840,8 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stats end - local function hasmetacopyassignment(typ) - if typ and typ:isstruct() and typ.metamethods.__copy then + local function hasmetacopyassignment(reciever) + if reciever.type and reciever.type:isstruct() and reciever.type.metamethods.__copy then return true end return false @@ -2849,7 +2849,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local function checkmetacopyassignment(anchor, from, to) --if neither `from` or `to` are a struct then return - if not (hasmetacopyassignment(from.type) or hasmetacopyassignment(to.type)) then + if not (hasmetacopyassignment(from) or hasmetacopyassignment(to)) then return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -2861,7 +2861,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --list of overloaded __copy metamethods local overloads = terra.newlist() local function checkoverload(v) - if hasmetacopyassignment(v.type) then + if hasmetacopyassignment(v) then overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) end end @@ -3304,9 +3304,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - local rhstype = rhs[i] and rhs[i].type - local lhstype = lhs[i].type - if rhstype and (hasmetacopyassignment(lhstype) or hasmetacopyassignment(rhstype)) then + if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetacopyassignment(lhs[i]) or hasmetacopyassignment(rhs[i])) then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) From d6773527040f0875bc6dca05e9e588012426931f Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 21 May 2024 17:38:08 +0200 Subject: [PATCH 09/69] added hasmetamethod(v, method). changed copy assignment behavior: deferred destructor call to data of lhs is performed after copy assignment. --- src/terralib.lua | 52 ++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index fdae97bdc..b94a5b703 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2762,6 +2762,15 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkcall(anchor, terra.newlist { fnlike }, fnargs, "first", false, location) end + --check if metamethod is implemented + local function hasmetamethod(v, method) + local typ = v.type + if typ and typ:isstruct() and typ.metamethods[method] then + return true + end + return false + end + --check if metamethods.__init is implemented local function checkmetainit(anchor, reciever) if reciever.type and reciever.type:isstruct() then @@ -2840,16 +2849,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stats end - local function hasmetacopyassignment(reciever) - if reciever.type and reciever.type:isstruct() and reciever.type.metamethods.__copy then - return true - end - return false - end - local function checkmetacopyassignment(anchor, from, to) --if neither `from` or `to` are a struct then return - if not (hasmetacopyassignment(from) or hasmetacopyassignment(to)) then + if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -2861,7 +2863,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --list of overloaded __copy metamethods local overloads = terra.newlist() local function checkoverload(v) - if hasmetacopyassignment(v) then + if hasmetamethod(v, "__copy") then overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) end end @@ -3304,7 +3306,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetacopyassignment(lhs[i]) or hasmetacopyassignment(rhs[i])) then + if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetamethod(lhs[i],"__copy") or hasmetamethod(rhs[i],"__copy")) then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) @@ -3357,6 +3359,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.assignment,lhs,rhs) else --standard case #lhs == #rhs + local stmts = terralib.newlist() --first take care of regular assignments local regular, byfcall = assignmentkinds(lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) @@ -3370,10 +3373,17 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) v:settype(rhstype) else ensurelvalue(v) + --if 'v' is a managed variable then first assign 'v' to a temporary variable 'var tmp = v' + --then do the reassignment, 'v = ...', and then, call the destructor on 'tmp', freeing possible + --heap resources + if hasmetamethod(v, "__dtor") then + local tmpv, tmp = allocvar(v, v.type,"") + stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) + stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) + end end end --take care of copy assignments using metamethods.__copy - local stmts = terralib.newlist() for i,v in ipairs(byfcall.lhs) do local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error if v:is "setteru" then @@ -3390,9 +3400,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) - local dtor = checkmetadtor(anchor, v) - if dtor then - stmts:insert(dtor) + --first assign 'v' to a temporary variable 'var tmp = v' (shallow copy) + --then do the reassignment, 'v = ...', and then call the destructor + --on 'tmp', freeing possible heap resources + if hasmetamethod(v, "__dtor") then + local tmpv, tmp = allocvar(v, v.type,"") + stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) + stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) end stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end @@ -3699,11 +3713,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From b77f4770cb9cf0df806d4beadf442dfe8e7a6d33 Mon Sep 17 00:00:00 2001 From: renehiemstra <152627545+renehiemstra@users.noreply.github.com> Date: Wed, 22 May 2024 08:11:37 +0200 Subject: [PATCH 10/69] forgot to uncomment SDKROOT code on macos --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b94a5b703..8740c28df 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3713,11 +3713,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 8ba635e48d51b65a1f1d86d4e562435f26b71a46 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 16:04:59 +0200 Subject: [PATCH 11/69] fixed small bug in copyconstruction where the type is passed, e.g 'var a : A = ...', by setting rhs type only when required. --- src/terralib.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b94a5b703..b17c95320 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2856,8 +2856,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end --if `to` is an allocvar then set type and turn into corresponding `var` if to:is "allocvar" then - local typ = from.type or terra.types.error - to:settype(typ) + if not to.type then + to:settype(from.type or terra.types.error) + end to = newobject(anchor,T.var,to.name,to.symbol):setlvalue(true):withtype(to.type) end --list of overloaded __copy metamethods @@ -3391,7 +3392,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], r)) stmts:insert(newobject(v,T.setter, rv, v.setter(r))) elseif v:is "allocvar" then - v:settype(rhstype) + if not v.type then + v:settype(rhstype) + end stmts:insert(v) local init = checkmetainit(anchor, v) if init then From 1ff703847955e68a4d35ed42d95a787c4dd4a4b0 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 16:36:43 +0200 Subject: [PATCH 12/69] tests/raii-copyctr-cast.t, which combines __cast and __copy metamethods. --- tests/raii-copyctr-cast.t | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/raii-copyctr-cast.t diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t new file mode 100644 index 000000000..325e3b6e7 --- /dev/null +++ b/tests/raii-copyctr-cast.t @@ -0,0 +1,88 @@ +--[[ + We need that direct initialization + var a : A = b + yields the same result as + var a : A + a = b + If 'b' is a variable or a literal (something with a value) and the user has + implemented the right copy-assignment 'A.metamethods.__copy' then the copy + should be perform using this metamethod. + If the metamethod is not implemented for the exact types then a (user-defined) + implicit cast should be attempted. +--]] + +local test = require("test") +local std = {} +std.io = terralib.includec("stdio.h") + +struct A{ + data : int +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.metamethods.__copy = terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = to.data + from.data + 10 +end + +A.metamethods.__cast = function(from, to, exp) + print("attempting cast from "..tostring(from).." --> "..tostring(to)) + if to == &A and from:ispointer() then + return quote + var tmp = A{@exp} + in + &tmp + end + end +end + +--[[ + The integer '2' will first be cast to a temporary of type A using + the user defined A.metamethods.__cast method. Then the metamethod + A.metamethods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.metamethods.__copy(from : &A, to : &A) + will be called to finalize the copy-construction. +--]] +terra testwithcast() + var a : A = 2 + return a.data +end + +-- to.data + from.data + 10 = 1 + 2 + 10 = 13 +test.eq(testwithcast(), 13) + + +A.metamethods.__copy = terralib.overloadedfunction("__copy") + +A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = to.data + from.data + 10 +end) + +A.metamethods.__copy:adddefinition(terra(from : int, to : &A) + std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") + to.data = to.data + from + 11 +end) + + +--[[ + The metamethod A.metamethods.__init(self : &A) is called to initialize + the variable and then the copy-constructor A.metamethods.__copy(from : int, to : &A) + will be called to finalize the copy-construction. +--]] +terra testwithoutcast() + var a : A = 2 + return a.data +end + +-- to.data + from.data + 10 = 1 + 2 + 11 = 14 +test.eq(testwithoutcast(), 14) \ No newline at end of file From 31a006b737f70222f4edf7ec2beaa87a2d18b158 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 22:04:11 +0200 Subject: [PATCH 13/69] raii-shared_ptr.t which tests some functionality for a shared pointer-like type. --- tests/raii-shared_ptr.t | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/raii-shared_ptr.t diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t new file mode 100644 index 000000000..8317620e7 --- /dev/null +++ b/tests/raii-shared_ptr.t @@ -0,0 +1,148 @@ +local test = require("test") + +local std = {} +std.io = terralib.includec("stdio.h") +std.lib = terralib.includec("stdlib.h") + +local function printtestdescription(s) + print() + print("======================================") + print(s) + print("======================================") +end + +--implementation of a smart (shared) pointer type +local function SharedPtr(T) + + local struct A{ + data : &T --underlying data ptr (reference counter is stored in its head) + } + + --table for static methods + local static_methods = {} + + A.metamethods.__getmethod = function(self, methodname) + return A.methods[methodname] or static_methods[methodname] or error("No method " .. methodname .. "defined on " .. self) + end + + 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 + + A.methods.__dereference = terra(self : &A) + return @self.data + end + + static_methods.new = terra() + std.io.printf("new: allocating memory. start\n") + defer std.io.printf("new: allocating memory. return.\n") + --heap allocation for `data` with the reference counter `refcount` stored in its head and the real data in its tail + var head = sizeof(int8) + var tail = sizeof(T) + var ptr = [&int8](std.lib.malloc(head+tail)) + --assign to data + var x = A{[&T](ptr+1)} + --initializing the reference counter to one + @x:refcounter() = 1 + return x + end + + --initialization of pointer + 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 + + --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`. + if @self:refcounter() == 1 then + --free memory if the last shared pointer obj runs out of life + 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:refcounter()) + self.data = nil --reinitialize data ptr + else + --otherwise reduce reference counter + self:decreaserefcounter() + 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(from : &A, to : &A) + std.io.printf("__copy: calling copy-assignment operator. start\n") + defer std.io.printf("__copy: calling copy-assignment operator. return\n") + to.data = from.data + to:increaserefcounter() + end + + --return parameterized shared pointer type + return A +end + +local shared_ptr_int = SharedPtr(int) + +--testing vardef and copy assign +local terra test0() + var a : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 10 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --10 * 2 + end +end + +--testing var and copy assign +local terra test1() + var a : shared_ptr_int, b : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 11 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --11 * 2 + end +end + +printtestdescription("shared_ptr - copy construction.") +test.eq(test0(), 20) + +printtestdescription("shared_ptr - copy assignment.") +test.eq(test1(), 22) \ No newline at end of file From ac0c68a2f0d225d08df078efe48aa01e24f354b6 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 23 May 2024 20:25:10 +0200 Subject: [PATCH 14/69] enabled copy assignments for rhs 'select' variables and rhs pointer variables. --- src/terralib.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 598344faa..b48103f3a 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3307,7 +3307,21 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetamethod(lhs[i],"__copy") or hasmetamethod(rhs[i],"__copy")) then + local cpassign = false + local r = rhs[i] + if r then + --alternatively, work on the r.type and check for + --r.type:isprimitive(), r.type:isstruct(), etc + if r:is "operator" and r.operator == "&" then + r = r.operands[1] + end + if r:is "var" or r:is "literal" or r:is "constant" or r:is "select" then + if hasmetamethod(lhs[i],"__copy") or hasmetamethod(r,"__copy") then + cpassign = true + end + end + end + if cpassign then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) From 8aee8b105391270cba20f957cee668370aa7a033 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 23 May 2024 20:26:34 +0200 Subject: [PATCH 15/69] raii-offset_ptr.t: example with a type that has some sematics of an offset pointer type, which has an overloaded __copy method. --- tests/raii-offset_ptr.t | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/raii-offset_ptr.t diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t new file mode 100644 index 000000000..71dc2279d --- /dev/null +++ b/tests/raii-offset_ptr.t @@ -0,0 +1,35 @@ +local test = require("test") + +local std = {} +std.io = terralib.includec("stdio.h") + +struct offset_ptr{ + offset : int + init : bool +} + +offset_ptr.metamethods.__copy = terra(from : &int64, to : &offset_ptr) + to.offset = [&int8](from) - [&int8](to) + to.init = true + std.io.printf("offset_ptr: __copy &int -> &offset_ptr\n") +end + +local struct A{ + integer_1 : int64 + integer_2 : int64 + ptr : offset_ptr +} + +terra test0() + var a : A + a.ptr = &a.integer_1 + var save_1 = a.ptr.offset + std.io.printf("value of a.ptr.offset: %d\n", a.ptr.offset) + a.ptr = &a.integer_2 + var save_2 = a.ptr.offset + std.io.printf("value of a.ptr.offset: %d\n", a.ptr.offset) + return save_1, save_2 +end + +--test the offset in bytes between ptr and the integers in struct A +test.meq({-16, -8},test0()) \ No newline at end of file From 94caf674c07778ee6a9a4acbc2c542cfb2431c1b Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Sun, 26 May 2024 15:29:01 +0200 Subject: [PATCH 16/69] added lib/terralibext.t that is being called from terralib to enable composable raii datastructures. Missing __init, __copy, __dtor methods are generated on the fly when needed. --- lib/terralibext.t | 214 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 lib/terralibext.t diff --git a/lib/terralibext.t b/lib/terralibext.t new file mode 100644 index 000000000..e60fbf2c0 --- /dev/null +++ b/lib/terralibext.t @@ -0,0 +1,214 @@ +local std = { + io = terralib.includec("stdio.h") +} + +local function ondemand(fn) + local method + return macro(function(self,...) + if not method then + method = fn() + end + local args = {...} + return `method(&self,[args]) + end) +end + +local function addmissinginit(T) + + local generate = false + + local runinit = macro(function(self) + local T = self:gettype() + --avoid generating code for empty array initializers + local function hasinit(T) + if T:isstruct() then return T.methods.__init + elseif T:isarray() then return hasinit(T.type) + else return false end + end + if T:isstruct() then + if not T.methods.__init then + addmissinginit(T) + end + if T.methods.__init then + return quote + self:__init() + end + end + elseif T:isarray() and hasinit(T) then + return quote + var pa = &self + for i = 0,T.N do + runinit((@pa)[i]) + end + end + elseif T:ispointer() then + return quote + self = nil + end + end + return quote end + end) + + local generateinit = macro(function(self) + local T = self:gettype() + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + if e.field then + local expr = `runinit(self.[e.field]) + if #expr.tree.statements > 0 then + generate = true + end + stmts:insert( + expr + ) + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__init then + local method = terra(self : &T) + std.io.printf("%s:__init - default\n", [tostring(T)]) + generateinit(@self) + end + if generate then + T.methods.__init = method + end + end +end + + +local function addmissingdtor(T) + + local generate = false + + local rundtor = macro(function(self) + local T = self:gettype() + --avoid generating code for empty array initializers + local function hasdtor(T) + if T:isstruct() then return T.methods.__dtor + elseif T:isarray() then return hasdtor(T.type) + else return false end + end + if T:isstruct() then + if not T.methods.__dtor then + addmissingdtor(T) + end + if T.methods.__dtor then + return quote + self:__dtor() + end + end + elseif T:isarray() and hasdtor(T) then + return quote + var pa = &self + for i = 0,T.N do + rundtor((@pa)[i]) + end + end + end + return quote end + end) + + local generatedtor = macro(function(self) + local T = self:gettype() + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + if e.field then + local expr = `rundtor(self.[e.field]) + if #expr.tree.statements > 0 then + generate = true + stmts:insert(expr) + end + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__dtor then + local method = terra(self : &T) + std.io.printf("%s:__dtor - default\n", [tostring(T)]) + generatedtor(@self) + end + if generate then + T.methods.__dtor = method + end + end +end + +local function addmissingcopy(T) + + local generate = false + + local runcopy = macro(function(from, to) + local V = from:gettype() + --avoid generating code for empty array initializers + local function hascopy(U) + if U:isstruct() then return U.methods.__copy + elseif U:isarray() then return hascopy(U.type) + else return false end + end + if V:isstruct() then + if not V.methods.__copy then + addmissingcopy(V) + end + local method = V.methods.__copy + if method then + generate = true + return quote + method(&from, &to) + end + else + return quote + to = from + end + end + elseif V:isarray() and hasdtor(V) then + return quote + var pa = &self + for i = 0,V.N do + rundtor((@pa)[i]) + end + end + else + return quote + to = from + end + end + return quote end + end) + + local generatecopy = macro(function(from, to) + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + local field = e.field + local expr = `runcopy(from.[field], to.[field]) + print(expr) + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__copy then + local method = terra(from : &T, to : &T) + std.io.printf("%s:__copy - default\n", [tostring(T)]) + generatecopy(@from, @to) + end + if generate then + T.methods.__copy = method + end + end +end + +terralib.ext = { + addmissing = { + __init = addmissinginit, + __dtor = addmissingdtor, + __copy = addmissingcopy + } +} \ No newline at end of file From 8cbc4b0ed56117b2bbeaa8f62eb372a721b6afa7 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Sun, 26 May 2024 20:02:42 +0200 Subject: [PATCH 17/69] changed __init, __copy, __dtor from metamethods to regular methods, such that they can potentially be called in sourcecode. --- src/terralib.lua | 18 +++++++++--------- tests/raii-copyctr-cast.t | 12 ++++++------ tests/raii-offset_ptr.t | 2 +- tests/raii-shared_ptr.t | 6 +++--- tests/raii-unique_ptr.t | 10 +++++----- tests/raii.t | 12 ++++++------ 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b48103f3a..0c070f9fa 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2765,20 +2765,20 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if metamethod is implemented local function hasmetamethod(v, method) local typ = v.type - if typ and typ:isstruct() and typ.metamethods[method] then + if typ and typ:isstruct() and typ.methods[method] then return true end return false end - --check if metamethods.__init is implemented + --check if methods.__init is implemented local function checkmetainit(anchor, reciever) if reciever.type and reciever.type:isstruct() then if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - if reciever.type.metamethods.__init then - return checkmethodwithreciever(anchor, true, "__init", reciever, terralib.newlist(), "statement") + if reciever.type.methods.__init then + return checkmethodwithreciever(anchor, false, "__init", reciever, terralib.newlist(), "statement") end end end @@ -2797,11 +2797,11 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if a __dtor metamethod is implemented for the type corresponding to `sym` local function checkmetadtor(anchor, reciever) if reciever.type and reciever.type:isstruct() then - if reciever.type.metamethods.__dtor then + if reciever.type.methods.__dtor then if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + return checkmethodwithreciever(anchor, false, "__dtor", reciever, terralib.newlist(), "statement") end end end @@ -2835,7 +2835,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --get position at which to add destructor statements local pos = rstat and #stats or #stats+1 for name,sym in pairs(env:localenv()) do - --if not a return variable ckeck for an implementation of metamethods.__dtor + --if not a return variable ckeck for an implementation of methods.__dtor if not rsyms[name] then local reciever = newobject(anchor,T.var, name, sym):setlvalue(true):withtype(sym.type) local dtor = checkmetadtor(anchor, reciever) @@ -2865,7 +2865,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local overloads = terra.newlist() local function checkoverload(v) if hasmetamethod(v, "__copy") then - overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) + overloads:insert(asterraexpression(anchor, v.type.methods.__copy, "luaobject")) end end --add overloaded methods based on left- and right-hand-side of the assignment @@ -3398,7 +3398,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end end - --take care of copy assignments using metamethods.__copy + --take care of copy assignments using methods.__copy for i,v in ipairs(byfcall.lhs) do local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error if v:is "setteru" then diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 325e3b6e7..5a14389fa 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -19,17 +19,17 @@ struct A{ data : int } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: calling initializer.\n") self.data = 1 end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor.\n") self.data = -1 end -A.metamethods.__copy = terra(from : &A, to : &A) +A.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = to.data + from.data + 10 end @@ -61,14 +61,14 @@ end test.eq(testwithcast(), 13) -A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) +A.methods.__copy:adddefinition(terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = to.data + from.data + 10 end) -A.metamethods.__copy:adddefinition(terra(from : int, to : &A) +A.methods.__copy:adddefinition(terra(from : int, to : &A) std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") to.data = to.data + from + 11 end) diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index 71dc2279d..72ba0d10c 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -8,7 +8,7 @@ struct offset_ptr{ init : bool } -offset_ptr.metamethods.__copy = terra(from : &int64, to : &offset_ptr) +offset_ptr.methods.__copy = terra(from : &int64, to : &offset_ptr) to.offset = [&int8](from) - [&int8](to) to.init = true std.io.printf("offset_ptr: __copy &int -> &offset_ptr\n") diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index 8317620e7..2802fc9ed 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -65,14 +65,14 @@ local function SharedPtr(T) end --initialization of pointer - A.metamethods.__init = terra(self : &A) + A.methods.__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 --destructor - A.metamethods.__dtor = terra(self : &A) + A.methods.__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 @@ -96,7 +96,7 @@ local function SharedPtr(T) --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(from : &A, to : &A) + A.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy-assignment operator. start\n") defer std.io.printf("__copy: calling copy-assignment operator. return\n") to.data = from.data diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 4d41257ef..782da16eb 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -14,14 +14,14 @@ struct A{ heap : bool } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: initializing object. start.\n") self.data = nil -- initialize data pointer to nil self.heap = false --flag to denote heap resource std.io.printf("__init: initializing object. return.\n") end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor. start\n") defer std.io.printf("__dtor: calling destructor. return\n") if self.heap then @@ -32,8 +32,8 @@ A.metamethods.__dtor = terra(self : &A) end end -A.metamethods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition( +A.methods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy:adddefinition( terra(from : &A, to : &A) std.io.printf("__copy: moving resources {&A, &A} -> {}.\n") to.data = from.data @@ -41,7 +41,7 @@ terra(from : &A, to : &A) from.data = nil from.heap = false end) -A.metamethods.__copy:adddefinition( +A.methods.__copy:adddefinition( terra(from : &int, to : &A) std.io.printf("__copy: assignment {&int, &A} -> {}.\n") to.data = from diff --git a/tests/raii.t b/tests/raii.t index 4482f3df3..f59795393 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -12,26 +12,26 @@ struct A{ data : int } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: calling initializer.\n") self.data = 1 end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor.\n") self.data = -1 end -A.metamethods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) +A.methods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy:adddefinition(terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = from.data + 10 end) -A.metamethods.__copy:adddefinition(terra(from : int, to : &A) +A.methods.__copy:adddefinition(terra(from : int, to : &A) std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") to.data = from end) -A.metamethods.__copy:adddefinition(terra(from : &A, to : &int) +A.methods.__copy:adddefinition(terra(from : &A, to : &int) std.io.printf("__copy: calling copy assignment {&A, &int} -> {}.\n") @to = from.data end) From 509816da447f6fbe90cba48d96202d7dbc367415 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:49:44 +0200 Subject: [PATCH 18/69] cleaned up terralibext.t --- lib/terralibext.t | 97 +++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index e60fbf2c0..0ff0d754e 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -2,46 +2,39 @@ local std = { io = terralib.includec("stdio.h") } -local function ondemand(fn) - local method - return macro(function(self,...) - if not method then - method = fn() - end - local args = {...} - return `method(&self,[args]) - end) -end - local function addmissinginit(T) + --flag that signals that a missing __init method needs to + --be generated local generate = false local runinit = macro(function(self) - local T = self:gettype() + local V = self:gettype() --avoid generating code for empty array initializers - local function hasinit(T) - if T:isstruct() then return T.methods.__init - elseif T:isarray() then return hasinit(T.type) + local function hasinit(U) + if U:isstruct() then return U.methods.__init + elseif U:isarray() then return hasinit(U.type) else return false end end - if T:isstruct() then - if not T.methods.__init then - addmissinginit(T) + if V:isstruct() then + if not V.methods.__init then + addmissinginit(V) end - if T.methods.__init then + local method = V.methods.__init + if method then + generate = true return quote self:__init() end end - elseif T:isarray() and hasinit(T) then + elseif V:isarray() and hasinit(V) then return quote var pa = &self for i = 0,T.N do runinit((@pa)[i]) end end - elseif T:ispointer() then + elseif V:ispointer() then return quote self = nil end @@ -56,12 +49,9 @@ local function addmissinginit(T) for i,e in ipairs(entries) do if e.field then local expr = `runinit(self.[e.field]) - if #expr.tree.statements > 0 then - generate = true + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) end - stmts:insert( - expr - ) end end return stmts @@ -69,7 +59,7 @@ local function addmissinginit(T) if T:isstruct() and not T.methods.__init then local method = terra(self : &T) - std.io.printf("%s:__init - default\n", [tostring(T)]) + std.io.printf("%s:__init - generated\n", [tostring(T)]) generateinit(@self) end if generate then @@ -81,26 +71,30 @@ end local function addmissingdtor(T) + --flag that signals that a missing __dtor method needs to + --be generated local generate = false local rundtor = macro(function(self) - local T = self:gettype() - --avoid generating code for empty array initializers - local function hasdtor(T) - if T:isstruct() then return T.methods.__dtor - elseif T:isarray() then return hasdtor(T.type) + local V = self:gettype() + --avoid generating code for empty array destructors + local function hasdtor(U) + if U:isstruct() then return U.methods.__dtor + elseif U:isarray() then return hasdtor(U.type) else return false end end - if T:isstruct() then - if not T.methods.__dtor then - addmissingdtor(T) + if V:isstruct() then + if not V.methods.__dtor then + addmissingdtor(V) end - if T.methods.__dtor then + local method = V.methods.__dtor + if method then + generate = true return quote self:__dtor() end end - elseif T:isarray() and hasdtor(T) then + elseif V:isarray() and hasdtor(V) then return quote var pa = &self for i = 0,T.N do @@ -118,8 +112,7 @@ local function addmissingdtor(T) for i,e in ipairs(entries) do if e.field then local expr = `rundtor(self.[e.field]) - if #expr.tree.statements > 0 then - generate = true + if expr and #expr.tree.statements > 0 then stmts:insert(expr) end end @@ -129,7 +122,7 @@ local function addmissingdtor(T) if T:isstruct() and not T.methods.__dtor then local method = terra(self : &T) - std.io.printf("%s:__dtor - default\n", [tostring(T)]) + std.io.printf("%s:__dtor - generated\n", [tostring(T)]) generatedtor(@self) end if generate then @@ -140,17 +133,20 @@ end local function addmissingcopy(T) + --flag that signals that a missing __copy method needs to + --be generated local generate = false local runcopy = macro(function(from, to) - local V = from:gettype() + local U = from:gettype() + local V = to:gettype() --avoid generating code for empty array initializers - local function hascopy(U) - if U:isstruct() then return U.methods.__copy - elseif U:isarray() then return hascopy(U.type) + local function hascopy(W) + if W:isstruct() then return W.methods.__copy + elseif W:isarray() then return hascopy(W.type) else return false end end - if V:isstruct() then + if V:isstruct() and U==V then if not V.methods.__copy then addmissingcopy(V) end @@ -185,10 +181,11 @@ local function addmissingcopy(T) local entries = T:getentries() for i,e in ipairs(entries) do local field = e.field - local expr = `runcopy(from.[field], to.[field]) - print(expr) - if expr and #expr.tree.statements > 0 then - stmts:insert(expr) + if field then + local expr = `runcopy(from.[field], to.[field]) + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) + end end end return stmts @@ -196,7 +193,7 @@ local function addmissingcopy(T) if T:isstruct() and not T.methods.__copy then local method = terra(from : &T, to : &T) - std.io.printf("%s:__copy - default\n", [tostring(T)]) + std.io.printf("%s:__copy - generate\n", [tostring(T)]) generatecopy(@from, @to) end if generate then From 0090b2e07680c23717370ea2c8ba4129a81afc2e Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:51:23 +0200 Subject: [PATCH 19/69] updated tests to incorporate changes in 'terralibext.t' --- tests/raii-copyctr-cast.t | 2 ++ tests/raii-offset_ptr.t | 2 ++ tests/raii-shared_ptr.t | 44 ++++++++++----------------------------- tests/raii-unique_ptr.t | 2 ++ tests/raii.t | 3 +++ 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 5a14389fa..501efc404 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" --[[ We need that direct initialization var a : A = b diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index 72ba0d10c..e761d3ac7 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local test = require("test") local std = {} diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index 2802fc9ed..f018e118d 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local test = require("test") local std = {} @@ -64,14 +66,12 @@ local function SharedPtr(T) return x end - --initialization of pointer A.methods.__init = terra(self : &A) - std.io.printf("__init: initializing object. start.\n") + std.io.printf("__init: initializing object\n") self.data = nil -- initialize data pointer to nil std.io.printf("__init: initializing object. return.\n") end - --destructor A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor. start\n") defer std.io.printf("__dtor: calling destructor. return\n") @@ -93,9 +93,6 @@ local function SharedPtr(T) 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.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy-assignment operator. start\n") defer std.io.printf("__copy: calling copy-assignment operator. return\n") @@ -109,40 +106,21 @@ end local shared_ptr_int = SharedPtr(int) ---testing vardef and copy assign +printtestdescription("shared_ptr - copy construction.") local terra test0() var a : shared_ptr_int - std.io.printf("main: a.refcount: %p\n", a:refcounter()) a = shared_ptr_int.new() - @a.data = 10 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()) - if a:refcounter()==b:refcounter() then - return @b.data * @a:refcounter() --10 * 2 - end -end - ---testing var and copy assign -local terra test1() - var a : shared_ptr_int, b : shared_ptr_int - std.io.printf("main: a.refcount: %p\n", a:refcounter()) - a = shared_ptr_int.new() - @a.data = 11 + @a.data = 10 std.io.printf("main: a.data: %d\n", @a.data) std.io.printf("main: a.refcount: %d\n", @a:refcounter()) - b = a + var b = a std.io.printf("main: b.data: %d\n", @b.data) std.io.printf("main: a.refcount: %d\n", @a:refcounter()) - if a:refcounter()==b:refcounter() then - return @b.data * @a:refcounter() --11 * 2 - end + --if a:refcounter()==b:refcounter() then + -- return @b.data * @a:refcounter() --10 * 2 + --end end - -printtestdescription("shared_ptr - copy construction.") -test.eq(test0(), 20) - -printtestdescription("shared_ptr - copy assignment.") -test.eq(test1(), 22) \ No newline at end of file +test0() +--test.eq(test0(), 20) \ No newline at end of file diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 782da16eb..7609b868c 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local std = {} std.io = terralib.includec("stdio.h") std.lib = terralib.includec("stdlib.h") diff --git a/tests/raii.t b/tests/raii.t index f59795393..3ba51c6ef 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -1,3 +1,6 @@ +--load 'terralibext' to enable raii +require "terralibext" + local std = {} std.io = terralib.includec("stdio.h") From 9bf3f7941e3bdef3d235c7b2cdb0862872589772 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:52:13 +0200 Subject: [PATCH 20/69] raii-compose.t: testing composable use of managed structs. --- tests/raii-compose.t | 152 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tests/raii-compose.t diff --git a/tests/raii-compose.t b/tests/raii-compose.t new file mode 100644 index 000000000..07f1d33de --- /dev/null +++ b/tests/raii-compose.t @@ -0,0 +1,152 @@ +--load 'terralibext' to enable raii +require "terralibext" + +local test = require "test" + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +local std = { + io = terralib.includec("stdio.h") +} + +--A is a managed struct, as it implements __init, __copy, __dtor +local struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("A.__init\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("A.__dtor\n") + self.data = -1 +end + +A.methods.__copy = terra(from : &A, to : &A) + std.io.printf("A.__copy\n") + to.data = from.data + 2 +end + +local struct B{ + data : int +} + +local struct C{ + data_a : A --managed + data_b : B --not managed +} + +local struct D{ + data_a : A --managed + data_b : B --not managed + data_c : C +} + +printtestheader("raii-compose.t - testing __init for managed struct") +local terra testinit_A() + var a : A + return a.data +end +test.eq(testinit_A(), 1) + +printtestheader("raii-compose.t - testing __init for managed field") +local terra testinit_C() + var c : C + return c.data_a.data +end +test.eq(testinit_C(), 1) + +printtestheader("raii-compose.t - testing __init for managed field and subfield") +local terra testinit_D() + var d : D + return d.data_a.data + d.data_c.data_a.data +end +test.eq(testinit_D(), 2) + +printtestheader("raii-compose.t - testing __dtor for managed struct") +local terra testdtor_A() + var x : &int + do + var a : A + x = &a.data + end + return @x +end +test.eq(testdtor_A(), -1) + +printtestheader("raii-compose.t - testing __dtor for managed field") +local terra testdtor_C() + var x : &int + do + var c : C + x = &c.data_a.data + end + return @x +end +test.eq(testdtor_C(), -1) + +printtestheader("raii-compose.t - testing __dtor for managed field and subfield") +local terra testdtor_D() + var x : &int + var y : &int + do + var d : D + x = &d.data_a.data + y = &d.data_c.data_a.data + end + return @x + @y +end +test.eq(testdtor_D(), -2) + +printtestheader("raii-compose.t - testing __copy for managed field") +terra testcopyassignment_C() + var c_1 : C + var c_2 : C + c_1.data_a.data = 5 + c_2 = c_1 + std.io.printf("value c_2._data_a.data %d\n", c_2.data_a.data) + return c_2.data_a.data +end +test.eq(testcopyassignment_C(), 5 + 2) + +printtestheader("raii-compose.t - testing __copy for managed field and subfield") +terra testcopyassignment_D() + var d_1 : D + var d_2 : D + d_1.data_a.data = 5 + d_1.data_c.data_a.data = 6 + d_2 = d_1 + std.io.printf("value d_2._data_a.data %d\n", d_2.data_a.data) + std.io.printf("value d_2._data_c.data_a.data %d\n", d_2.data_c.data_a.data) + return d_2.data_a.data + d_2.data_c.data_a.data +end +test.eq(testcopyassignment_D(), 5 + 2 + 6 + 2) + +printtestheader("raii-compose.t - testing __copy construction for managed field") +terra testcopyconstruction_C() + var c_1 : C + c_1.data_a.data = 5 + var c_2 : C = c_1 + std.io.printf("value c_2._data_a.data %d\n", c_2.data_a.data) + return c_2.data_a.data +end +test.eq(testcopyconstruction_C(), 5 + 2) + +printtestheader("raii-compose.t - testing __copy construction for managed field and subfield") +terra testcopyconstruction_D() + var d_1 : D + d_1.data_a.data = 5 + d_1.data_c.data_a.data = 6 + var d_2 = d_1 + std.io.printf("value d_2._data_a.data %d\n", d_2.data_a.data) + std.io.printf("value d_2._data_c.data_a.data %d\n", d_2.data_c.data_a.data) + return d_2.data_a.data + d_2.data_c.data_a.data +end +test.eq(testcopyconstruction_D(), 5 + 2 + 6 + 2) From e1d9cf047b8ae2d87edb63549f1cf29d92dac3ac Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:53:34 +0200 Subject: [PATCH 21/69] first implementation of raii composable managed datastructures --- src/terralib.lua | 73 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 0c070f9fa..05e0cb2d4 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2764,6 +2764,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if metamethod is implemented local function hasmetamethod(v, method) + if not terralib.ext then return false end local typ = v.type if typ and typ:isstruct() and typ.methods[method] then return true @@ -2773,17 +2774,24 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if methods.__init is implemented local function checkmetainit(anchor, reciever) - if reciever.type and reciever.type:isstruct() then - if reciever:is "allocvar" then - reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) - end - if reciever.type.methods.__init then + if not terralib.ext then return end + local typ = reciever.type + if typ and typ:isstruct() then + --try to add missing __init method + if not typ.methods.__init then + terralib.ext.addmissing.__init(typ) + end + if typ.methods.__init then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(typ) + end return checkmethodwithreciever(anchor, false, "__init", reciever, terralib.newlist(), "statement") end end end local function checkmetainitializers(anchor, lhs) + if not terralib.ext then return end local stmts = terralib.newlist() for i,e in ipairs(lhs) do local init = checkmetainit(anchor, e) @@ -2796,10 +2804,16 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if a __dtor metamethod is implemented for the type corresponding to `sym` local function checkmetadtor(anchor, reciever) - if reciever.type and reciever.type:isstruct() then - if reciever.type.methods.__dtor then + if not terralib.ext then return end + local typ = reciever.type + if typ and typ:isstruct() then + --try to add missing __dtor method + if not typ.methods.__dtor then + terralib.ext.addmissing.__dtor(typ) + end + if typ.methods.__dtor then if reciever:is "allocvar" then - reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(typ) end return checkmethodwithreciever(anchor, false, "__dtor", reciever, terralib.newlist(), "statement") end @@ -2807,6 +2821,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end local function checkmetadtors(anchor, stats) + if not terralib.ext then return stats end --extract the return statement from `stats`, if there is one local function extractreturnstat() local n = #stats @@ -2850,8 +2865,24 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end local function checkmetacopyassignment(anchor, from, to) - --if neither `from` or `to` are a struct then return - if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then + if not terralib.ext then return end + local ftype, ttype = from.type, to.type + if (ftype and ftype:isstruct()) or (ttype and ttype:isstruct()) then + --case of equal struct types + if ftype == ttype then + if not ftype.methods.__copy then + --try add missing __copy method + terralib.ext.addmissing.__copy(ftype) + end + --if __copy was unsuccessful return to do regular copy + if not (ftype.methods.__copy) then return end + else + --otherwise + if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then return end + end + else + --only struct types are managed + --resort to regular copy return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -3303,11 +3334,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end --divide assignment into regular assignments and copy assignments - local function assignmentkinds(lhs, rhs) + local function assignmentkinds(anchor, lhs, rhs) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do local cpassign = false + --ToDo: for now we call 'checkmetacopyassignment' twice. Refactor with 'createassignment' local r = rhs[i] if r then --alternatively, work on the r.type and check for @@ -3316,7 +3348,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) r = r.operands[1] end if r:is "var" or r:is "literal" or r:is "constant" or r:is "select" then - if hasmetamethod(lhs[i],"__copy") or hasmetamethod(r,"__copy") then + if checkmetacopyassignment(anchor, r, lhs[i]) then cpassign = true end end @@ -3376,7 +3408,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --standard case #lhs == #rhs local stmts = terralib.newlist() --first take care of regular assignments - local regular, byfcall = assignmentkinds(lhs, rhs) + local regular, byfcall = assignmentkinds(anchor, lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) regular.rhs = insertcasts(anchor, vtypes, regular.rhs) for i,v in ipairs(regular.lhs) do @@ -3531,7 +3563,10 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return createassignment(s,lhs,rhs) else local res = createstatementlist(s,lhs) - res.statements:insertall(checkmetainitializers(s, lhs)) + local ini = checkmetainitializers(s, lhs) + if ini then + res.statements:insertall(ini) + end return res end elseif s:is "assignment" then @@ -3730,11 +3765,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 49170c62c32798c32fe9ec6e7e0187e73262b5a7 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:36:41 +0200 Subject: [PATCH 22/69] updated tests/raii-shared_ptr.t --- tests/raii-shared_ptr.t | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index f018e118d..18ab3b56b 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -109,18 +109,34 @@ local shared_ptr_int = SharedPtr(int) printtestdescription("shared_ptr - copy construction.") local terra test0() var a : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) a = shared_ptr_int.new() - std.io.printf("main: a.data: %d\n", @a.data) - std.io.printf("main: a.refcount: %d\n", @a:refcounter()) @a.data = 10 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()) - --if a:refcounter()==b:refcounter() then - -- return @b.data * @a:refcounter() --10 * 2 - --end + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --10 * 2 + end +end +test.eq(test0(), 20) + +printtestdescription("shared_ptr - copy assignment.") +local terra test1() + var a : shared_ptr_int, b : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 11 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --11 * 2 + end end -test0() ---test.eq(test0(), 20) \ No newline at end of file +test.eq(test1(), 22) + From 2e3dcf3a2bed361c1949d41345a28c482796413f Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:38:20 +0200 Subject: [PATCH 23/69] fixed assignment__copy assignment. removed __dtor in custom __copy assignment call. resource handling is in the hands of the programmer in case of __copy. --- src/terralib.lua | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 05e0cb2d4..523cd0082 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3363,6 +3363,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) regular.rhs:insert(rhs[i]) end end + if #byfcall>0 and #byfcall+#regular>1 then + --__copy can potentially mutate left and right-handsides in an + --assignment. So we prohibit assignments that may involve something + --like a swap: u,v = v, u. + --for now we prohibit this by limiting such assignments + erroratlocation(anchor, "a custom __copy assignment is not supported for tuples.") + end return regular, byfcall end @@ -3407,6 +3414,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else --standard case #lhs == #rhs local stmts = terralib.newlist() + local post = terralib.newlist() --first take care of regular assignments local regular, byfcall = assignmentkinds(anchor, lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) @@ -3420,13 +3428,19 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) v:settype(rhstype) else ensurelvalue(v) - --if 'v' is a managed variable then first assign 'v' to a temporary variable 'var tmp = v' - --then do the reassignment, 'v = ...', and then, call the destructor on 'tmp', freeing possible - --heap resources + --if 'v' is a managed variable then + --(1) var tmp_v : v.type = rhs[i] --evaluate the rhs and store in tmp_v + --(2) v:__dtor() --delete old v + --(3) v = tmp_v --copy new data to v + --these steps are needed because rhs[i] may be a function of v, and the assignment in 'anchor' + --could involve something like a swap: u, v = v, u if hasmetamethod(v, "__dtor") then - local tmpv, tmp = allocvar(v, v.type,"") - stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) - stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) + local tmpa_v, tmp_v = allocvar(v, v.type,"") + --define temporary variable as new left-hand-side + regular.lhs[i] = tmpa_v + --call v:__dtor() and set v = tmp_v + post:insert(checkmetadtor(anchor, v)) + post:insert(newobject(anchor,T.assignment, List{v}, List{tmp_v})) end end end @@ -3449,18 +3463,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) - --first assign 'v' to a temporary variable 'var tmp = v' (shallow copy) - --then do the reassignment, 'v = ...', and then call the destructor - --on 'tmp', freeing possible heap resources - if hasmetamethod(v, "__dtor") then - local tmpv, tmp = allocvar(v, v.type,"") - stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) - stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) - end + --apply copy assignment - memory resource management is in the + --hands of the programmer stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end end - if #stmts==0 then + if #stmts==0 and #post==0 then --standard case, no meta-copy-assignments return newobject(anchor,T.assignment, regular.lhs, regular.rhs) else @@ -3469,6 +3477,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if #regular.lhs>0 then stmts:insert(newobject(anchor,T.assignment, regular.lhs, regular.rhs)) end + stmts:insertall(post) return createstatementlist(anchor, stmts) end end From 4e6bfc3919f43b12c3660de734f0150c99d081d5 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:53:11 +0200 Subject: [PATCH 24/69] reorganized raii tests. --- tests/raii-copyctr-cast.t | 28 +++++++++++++--------------- tests/raii-offset_ptr.t | 8 ++++---- tests/raii-unique_ptr.t | 24 ++++++++++++------------ 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 501efc404..ca31c9031 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -1,5 +1,4 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii --[[ We need that direct initialization var a : A = b @@ -7,15 +6,16 @@ require "terralibext" var a : A a = b If 'b' is a variable or a literal (something with a value) and the user has - implemented the right copy-assignment 'A.metamethods.__copy' then the copy - should be perform using this metamethod. - If the metamethod is not implemented for the exact types then a (user-defined) + implemented the right copy-assignment 'A.methods.__copy' then the copy + should be performed using this method. + If the method is not implemented for the exact types then a (user-defined) implicit cast should be attempted. --]] local test = require("test") -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} struct A{ data : int @@ -49,9 +49,9 @@ end --[[ The integer '2' will first be cast to a temporary of type A using - the user defined A.metamethods.__cast method. Then the metamethod - A.metamethods.__init(self : &A) is called to initialize the variable - and then the copy-constructor A.metamethods.__copy(from : &A, to : &A) + the user defined A.metamethods.__cast method. Then the method + A.methods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.methods.__copy(from : &A, to : &A) will be called to finalize the copy-construction. --]] terra testwithcast() @@ -75,16 +75,14 @@ A.methods.__copy:adddefinition(terra(from : int, to : &A) to.data = to.data + from + 11 end) - --[[ - The metamethod A.metamethods.__init(self : &A) is called to initialize - the variable and then the copy-constructor A.metamethods.__copy(from : int, to : &A) - will be called to finalize the copy-construction. + The method A.methods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.methods.__copy(from : int, to : &A) will be + called to finalize the copy-construction. --]] terra testwithoutcast() var a : A = 2 return a.data end - -- to.data + from.data + 10 = 1 + 2 + 11 = 14 test.eq(testwithoutcast(), 14) \ No newline at end of file diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index e761d3ac7..a06cdbf87 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -1,9 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii local test = require("test") -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} struct offset_ptr{ offset : int diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 7609b868c..7275e5cb0 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -1,8 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" -local std = {} -std.io = terralib.includec("stdio.h") -std.lib = terralib.includec("stdlib.h") +require "terralibext" --load 'terralibext' to enable raii + +local std = { + io = terralib.includec("stdio.h"), + lib = terralib.includec("stdlib.h") +} local function printtestheader(s) print() @@ -67,12 +68,17 @@ terra A.methods.allocate(self : &A) self.heap = true end + +local test = require "test" + +printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") terra testdereference() var ptr : A ptr:allocate() ptr:setvalue(3) return ptr:getvalue() end +test.eq(testdereference(), 3) terra returnheapresource() var ptr : A @@ -81,17 +87,11 @@ terra returnheapresource() return ptr end +printtestheader("raii-unique_ptr.t: test return heap resource from function") terra testgetptr() var ptr = returnheapresource() return ptr:getvalue() end - -local test = require "test" - -printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") -test.eq(testdereference(), 3) - -printtestheader("raii-unique_ptr.t: test return heap resource from function") test.eq(testgetptr(), 3) From 95a7e84ca3798fbe21cc84a60e6939370a24e886 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 23:31:05 +0200 Subject: [PATCH 25/69] raii-tuple-default-copy.t and fails/raii-tuple-custom-copy.t --- tests/fails/raii-tuple-custom-copy.t | 42 ++++++++++++++++++++++++++ tests/raii-tuple-default-copy.t | 42 ++++++++++++++++++++++++++ tests/raii.t | 45 ++++++++++------------------ 3 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 tests/fails/raii-tuple-custom-copy.t create mode 100644 tests/raii-tuple-default-copy.t diff --git a/tests/fails/raii-tuple-custom-copy.t b/tests/fails/raii-tuple-custom-copy.t new file mode 100644 index 000000000..71a71a08e --- /dev/null +++ b/tests/fails/raii-tuple-custom-copy.t @@ -0,0 +1,42 @@ +require "terralibext" --load 'terralibext' to enable raii + +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.methods.__copy = terra(from : &A, to : &A) + std.io.printf("__copy: calling custom copy.\n") + to.data = from.data+1 +end + +printtestheader("raii.t - testing custom copy for tuples") +terra test0() + var a = A{1} + var b = A{2} + a, b = b, a + --tuple assignments are prohibited when __copy is implemented + --because proper resource management cannot be guaranteed + --(at least not yet) + return a.data, b.data +end +test0() \ No newline at end of file diff --git a/tests/raii-tuple-default-copy.t b/tests/raii-tuple-default-copy.t new file mode 100644 index 000000000..83a6eee6f --- /dev/null +++ b/tests/raii-tuple-default-copy.t @@ -0,0 +1,42 @@ +require "terralibext" --load 'terralibext' to enable raii +local test = require "test" + +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +printtestheader("raii.t - testing default copy metamethod") +terra test0() + var a = A{1} + var b = A{2} + a, b = b, a --bitcopies should work in a swap + --the following code is generated + --var tmp_a = __move(b) --store evaluated rhs in a tmp variable + --var tmp_b = __move(a) --store evaluated rhs in a tmp variable + --a:__dtor() --delete old memory (nothing happens as 'a' has been moved from) + --b:__dtor() --delete old memory (nothing happens as 'a' has been moved from) + --a = __move(tmp_a) --move new data into 'a' + --b = __move(tmp_b) --move new data into 'b' + return a.data, b.data +end +test.meq({2, 1}, test0()) \ No newline at end of file diff --git a/tests/raii.t b/tests/raii.t index 3ba51c6ef..96542c6ae 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -1,8 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii +local test = require "test" -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} local function printtestheader(s) print() @@ -40,11 +41,14 @@ A.methods.__copy:adddefinition(terra(from : &A, to : &int) end) +printtestheader("raii.t - testing __init metamethod") terra testinit() var a : A return a.data end +test.eq(testinit(), 1) +printtestheader("raii.t - testing __dtor metamethod") terra testdtor() var x : &int do @@ -53,13 +57,17 @@ terra testdtor() end return @x end +test.eq(testdtor(), -1) +printtestheader("raii.t - testing __copy metamethod in copy-construction") terra testcopyconstruction() var a : A var b = a return b.data end +test.eq(testcopyconstruction(), 11) +printtestheader("raii.t - testing __copy metamethod in copy-assignment") terra testcopyassignment() var a : A a.data = 2 @@ -67,13 +75,17 @@ terra testcopyassignment() b = a return b.data end +test.eq(testcopyassignment(), 12) +printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") terra testcopyassignment1() var a : A a = 3 return a.data end +test.eq(testcopyassignment1(), 3) +printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") terra testcopyassignment2() var a : A var x : int @@ -81,29 +93,4 @@ terra testcopyassignment2() x = a return x end - -local test = require "test" - ---test if __init is called on object initialization to set 'a.data = 1' -printtestheader("raii.t - testing __init metamethod") -test.eq(testinit(), 1) - ---test if __dtor is called at the end of the scope to set 'a.data = -1' -printtestheader("raii.t - testing __dtor metamethod") -test.eq(testdtor(), -1) - ---test if __copy is called in construction 'var b = a' -printtestheader("raii.t - testing __copy metamethod in copy-construction") -test.eq(testcopyconstruction(), 11) - ---test if __copy is called in an assignment 'b = a' -printtestheader("raii.t - testing __copy metamethod in copy-assignment") -test.eq(testcopyassignment(), 12) - ---test if __copy is called in an assignment 'a = 3' -printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") -test.eq(testcopyassignment1(), 3) - ---test if __copy is called in an assignment 'x = a' for integer x -printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") test.eq(testcopyassignment2(), 5) \ No newline at end of file From f4caa8b76bfee8f8b350aba65d856c5589a463ce Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 23:32:27 +0200 Subject: [PATCH 26/69] fixed throwing error in createassignment in case of a tuple assignment of managed variables with a custom copy method. --- src/terralib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 523cd0082..e0f6253f5 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3363,7 +3363,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) regular.rhs:insert(rhs[i]) end end - if #byfcall>0 and #byfcall+#regular>1 then + if #byfcall.lhs>0 and #byfcall.lhs+#regular.lhs>1 then --__copy can potentially mutate left and right-handsides in an --assignment. So we prohibit assignments that may involve something --like a swap: u,v = v, u. From 25cb5019178c6b6bd0e6bff0d5fe170be0ed419c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 28 May 2024 00:21:26 +0200 Subject: [PATCH 27/69] proper error exception for tuple assignment of managed variables. see test fails/raii-tuple-custom-copy.t --- src/terralib.lua | 2 +- tests/fails/raii-tuple-custom-copy.t | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index e0f6253f5..7eea24583 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3368,7 +3368,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --assignment. So we prohibit assignments that may involve something --like a swap: u,v = v, u. --for now we prohibit this by limiting such assignments - erroratlocation(anchor, "a custom __copy assignment is not supported for tuples.") + diag:reporterror(anchor, "a custom __copy assignment is not supported for tuples.") end return regular, byfcall end diff --git a/tests/fails/raii-tuple-custom-copy.t b/tests/fails/raii-tuple-custom-copy.t index 71a71a08e..5cbabb5a3 100644 --- a/tests/fails/raii-tuple-custom-copy.t +++ b/tests/fails/raii-tuple-custom-copy.t @@ -1,14 +1,9 @@ +if not require("fail") then return end require "terralibext" --load 'terralibext' to enable raii local std = {} std.io = terralib.includec("stdio.h") -local function printtestheader(s) - print() - print("===========================") - print(s) - print("===========================") -end struct A{ data : int @@ -29,7 +24,6 @@ A.methods.__copy = terra(from : &A, to : &A) to.data = from.data+1 end -printtestheader("raii.t - testing custom copy for tuples") terra test0() var a = A{1} var b = A{2} From dd7754fb913a1da796d2e3a7896cadf382b40efa Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 28 May 2024 00:25:23 +0200 Subject: [PATCH 28/69] system code macos in terralib.lua --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7eea24583..481155e05 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3774,11 +3774,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 5415fe28e9b32e87bce6bd587e80847916a5fd85 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 13:10:43 +0200 Subject: [PATCH 29/69] lib/raii.md - discussing implementation and use of RAII concepts. --- lib/raii.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 lib/raii.md diff --git a/lib/raii.md b/lib/raii.md new file mode 100644 index 000000000..ca4b27f51 --- /dev/null +++ b/lib/raii.md @@ -0,0 +1,137 @@ +# RAII - Resource management +Resource acquisition is initialization ([RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)) provides a deterministic means of safe resource management. It is generally associated with systems programming languages such as *c++* and *rust*. + +In the following I summarize the experimental implementation that you can find [here](https://github.com/renehiemstra/terra/tree/raii). The socalled [Big Three](https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) are supported: +* object destruction +* copy assignment +* copy construction + +Terra does not support rvalue references (introduced e.g. in *c++11*), so the experimental RAII support is comparable to that of *C++03* or *rust*. + +## Feature summary +Compiler support for the following methods: +``` +A.methods.__init(self : &A) +A.methods.__dtor(self : &A) +(A or B).methods.__copy(from : &A, to : &B) +``` +These methods support the implementation of smart containers and smart pointers, like *std::string*, *std::vector* and *std::unique_ptr*, *std::shared_ptr*, *boost:offset_ptr* in C++. + +The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular C stdlib functions such malloc and free, leaving memory allocation in the hands of the programmer. + +If implemented, these methods are inserted judiciously during the type checking phase, implemented in *terralib.lua*. All these metamethods can be implemented as macro's or as terra functions. + +## Compiler supported methods for RAII +A managed type is one that implements at least `__dtor` and optionally `__init` and `__copy` or, by induction, has fields or subfields that are of a managed type. In the following I assume `struct A` is a managed type. + +### Object initialization +`__init` is used to initialize managed variables: +``` + A.methods.__init(self : &A) +``` +The compiler checks for an `__init` method in any variable definition statement, without explicit initializer, and emits the call right after the variable definition, e.g. +``` + var a : A + a:__init() +``` +### Copy assignment +`__copy` enables specialized copy-assignment and, combined with `__init`, copy construction. `__copy` takes two arguments, which can be different, as long as one of them is a managed type, e.g. +``` + A.metamethods.__copy(from : &A, to : &B) +``` +and / or +``` + A.metamethods.__copy(from : &B, to : &A) +``` +If `a : A` is a managed type, then the compiler will replace a regular assignment by a call to the implemented `__copy` method +``` + b = a ----> A.methods.__copy(a, b) +``` +or +``` + a = b ----> A.methods.__copy(b, a) +``` +`__copy` can be an overloaded terra function or a macro. + +The programmer is responsable for managing any heap resources associated with the arguments of the `__copy` method. + +### Copy construction +In object construction, `__copy` is combined with `__init` to perform copy construction. For example, +``` + var b = a +``` +is replaced by the following statements +``` + var b : B + b:__init() --generated by compiler if an `__init` is implemented + A.methods.__copy(a, b) +``` +If the right `__copy` method is not implemented but a user defined `__cast` metamethod exists that can cast one of the arguments to the correct type, then the cast is performed and then the relevant copy method is applied. + +### Object destruction +`__dtor` can be used to free heap memory +``` + A.methods.__dtor(self : &A) +``` +The implementation adds a deferred call to `__dtor ` near the end of a scope, right before a potential return statement, for all variables local to the current scope that are not returned. Hence, `__dtor` is tied to the lifetime of the object. For example, for a block of code the compiler would generate +``` +do + var x : A, y : A + ... + ... + defer x:__dtor() --generated by compiler + defer y:__dtor() --generated by compiler +end +``` +or in case of a terra function +``` +terra foo(x : A) + var y : A, z : A + ... + ... + defer z:__dtor() --generated by compiler + return y +end +``` +`__dtor` is also called before any regular assignment (if a __copy method is not implemented) to free 'old' resources. So +``` + a = b +``` +is replaced by +``` + a:__dtor() --generated by compiler + a = b +``` +## Compositional API's +If a struct has fields or subfields that are managed types, but do not implement `__init`, `__copy` or `__dtor`, then the compiler will generate default methods that inductively call existing `__init`, `__copy` or `__dtor` methods for its fields and subfields. This enables compositional API's like `vector(vector(int))` or `vector(string)`. This is implemented as an extension to *terralib.lua* in *lib/terralibext.t*. + +## Current limitations +* Tuple (copy) assignement (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as +``` + a, b = b, a +``` +* Currently, there is no way to prevent unwanted calls to `__dtor` in cases such as the following. Consider +``` +terra foo() + var b : A + return bar(b) +end +``` +which will get expanded to +``` +terra foo() + var b : A + defer b:__dtor() --generated by compiler + return bar(b) +end +``` +If `bar` would return `b` then its associated heap resources would be released before they can be used in the outer scope. + +## Roadmap +The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is: +* support for *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. +* borrow checking (similar to *rust*) by counting, at compile time, the number of references. + + +## Examples +The check for metamethods.__dtor is done once in checkblock(...) (which checks a scoped environment) and metamethods.(__init, __copy, __dtor) are checked in several parts of checkassignment(...). These checks are cheap, especially if none of the metamethods are implemented. \ No newline at end of file From ced17eda7867c3d28a964d66e8ca03365d3fd1cc Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:24:26 +0200 Subject: [PATCH 30/69] added test artifacts to .ignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index d3d42d9f3..3bd7b8ebc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /build +/cmake-build-debug /docs/_site /docs/_vendor /install @@ -18,6 +19,13 @@ /tests/dynlib /tests/dynlib.exe /tests/not_bc +/tests/class2 +/tests/inline_c.exe +/tests/objc +/tests/objc2 +/tests/stdio.exe + +/.idea *.bc *.ll From 88771116606b83a4e978f503b9e6f34a8e1bd632 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:31:22 +0200 Subject: [PATCH 31/69] added __init metamethod to initialize managed types in raii. --- src/terralib.lua | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index cc6b2d60a..16dda343c 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2762,6 +2762,29 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkcall(anchor, terra.newlist { fnlike }, fnargs, "first", false, location) end + --check if metamethods.__init is implemented + local function checkmetainit(anchor, reciever) + if reciever.type and reciever.type:isstruct() then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + end + if reciever.type.metamethods.__init then + return checkmethodwithreciever(anchor, true, "__init", reciever, terralib.newlist(), "statement") + end + end + end + + local function checkmetainitializers(anchor, lhs) + local stmts = terralib.newlist() + for i,e in ipairs(lhs) do + local init = checkmetainit(anchor, e) + if init then + stmts:insert(init) + end + end + return stmts + end + local function checkmethod(exp, location) local methodname = checklabel(exp.name,true).value assert(type(methodname) == "string" or terra.islabel(methodname)) @@ -3309,9 +3332,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif s:is "defvar" then local rhs = s.hasinit and checkexpressions(s.initializers) local lhs = checkformalparameterlist(s.variables, not s.hasinit) - local res = s.hasinit and createassignment(s,lhs,rhs) - or createstatementlist(s,lhs) - return res + if s.hasinit then + return createassignment(s,lhs,rhs) + else + local res = createstatementlist(s,lhs) + res.statements:insertall(checkmetainitializers(s, lhs)) + return res + end elseif s:is "assignment" then local rhs = checkexpressions(s.rhs) local lhs = checkexpressions(s.lhs,"lexpression") From b760837401ed93f59fb2412f04b1f622edb3429b Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:32:07 +0200 Subject: [PATCH 32/69] added __dtor metamethod to destruct managed variables in raii. --- src/terralib.lua | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 16dda343c..7fb4320f4 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3247,9 +3247,59 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.assignment,lhs,rhs) end + local function checkmetadtors(anchor, stats) + --extract the return statement from `stats`, if there is one + local function extractreturnstat() + local n = #stats + if n>0 then + local s = stats[n] + if s:is "returnstat" then + return s + end + end + end + local rstat = extractreturnstat() + --extract the returned `var` symbols from a return statement + local function extractreturnedsymbols() + local ret = {} + --loop over expressions in a `letin` return statement + for i,v in ipairs(rstat.expression.expressions) do + if v:is "var" then + ret[v.name] = v.symbol + end + end + return ret + end + --check if a __dtor metamethod is implemented for the type corresponding to `sym` + local function checkdtor(name,sym) + local mt = sym.type.metamethods + if mt and mt.__dtor then + local reciever = newobject(anchor, T.var, name, sym):setlvalue(true):withtype(sym.type) + return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + end + end + + --get symbols that are returned in case of a return statement + local rsyms = rstat and extractreturnedsymbols() or {} + --get position at which to add destructor statements + local pos = rstat and #stats or #stats+1 + for name,sym in pairs(env:localenv()) do + --if not a return variable ckeck for an implementation of metamethods.__dtor + if not rsyms[name] then + local dtor = checkdtor(name,sym) + if dtor then + --add deferred calls to the destructors + table.insert(stats, pos, newobject(anchor, T.defer, dtor)) + pos = pos + 1 + end + end + end + return stats + end + function checkblock(s) env:enterblock() - local stats = checkstmts(s.statements) + local stats = checkmetadtors(s, checkstmts(s.statements)) env:leaveblock() return s:copy {statements = stats} end From c503201edf51f9cb6939928c6d755545b1132296 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:40:51 +0200 Subject: [PATCH 33/69] added __copy metamethod which enables specialized copy-assignment and construction. --- src/terralib.lua | 129 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 11 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7fb4320f4..d2e1d6f6b 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2785,6 +2785,39 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stmts end + local function hasmetacopyassignment(typ) + if typ and typ:isstruct() and typ.metamethods.__copy then + return true + end + return false + end + + local function checkmetacopyassignment(anchor, from, to) + --if neither `from` or `to` are a struct then return + if not (hasmetacopyassignment(from.type) or hasmetacopyassignment(to.type)) then + return + end + --if `to` is an allocvar then set type and turn into corresponding `var` + if to:is "allocvar" then + local typ = from.type or terra.types.error + to:settype(typ) + to = newobject(anchor,T.var,to.name,to.symbol):setlvalue(true):withtype(to.type) + end + --list of overloaded __copy metamethods + local overloads = terra.newlist() + local function checkoverload(v) + if hasmetacopyassignment(v.type) then + overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) + end + end + --add overloaded methods based on left- and right-hand-side of the assignment + checkoverload(from) + checkoverload(to) + if #overloads > 0 then + return checkcall(anchor, overloads, terralib.newlist{from, to}, "all", true, "expression") + end + end + local function checkmethod(exp, location) local methodname = checklabel(exp.name,true).value assert(type(methodname) == "string" or terra.islabel(methodname)) @@ -3211,7 +3244,28 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.letin, stmts, List {}, true):withtype(terra.types.unit) end + --divide assignment into regular assignments and copy assignments + local function assignmentkinds(lhs, rhs) + local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} + local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} + for i=1,#lhs do + local rhstype = rhs[i] and rhs[i].type + local lhstype = lhs[i].type + if rhstype and (hasmetacopyassignment(lhstype) or hasmetacopyassignment(rhstype)) then + --add assignment by __copy call + byfcall.lhs:insert(lhs[i]) + byfcall.rhs:insert(rhs[i]) + else + --default to regular assignment + regular.lhs:insert(lhs[i]) + regular.rhs:insert(rhs[i]) + end + end + return regular, byfcall + end + local function createassignment(anchor,lhs,rhs) + --special case where a rhs struct is unpacked if #lhs > #rhs and #rhs > 0 then local last = rhs[#rhs] if last.type:isstruct() and last.type.convertible == "tuple" and #last.type.entries + #rhs - 1 == #lhs then @@ -3231,20 +3285,73 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return createstatementlist(anchor, List {a1, a2}) end end - local vtypes = lhs:map(function(v) return v.type or "passthrough" end) - rhs = insertcasts(anchor,vtypes,rhs) - for i,v in ipairs(lhs) do - local rhstype = rhs[i] and rhs[i].type or terra.types.error - if v:is "setteru" then - local rv,r = allocvar(v,rhstype,"") - lhs[i] = newobject(v,T.setter, rv,v.setter(r)) - elseif v:is "allocvar" then - v:settype(rhstype) + + if #lhs < #rhs then + --an error may be reported later during type-checking: 'expected #lhs parameters (...), but found #rhs (...)' + local vtypes = lhs:map(function(v) return v.type or "passthrough" end) + rhs = insertcasts(anchor, vtypes, rhs) + for i,v in ipairs(lhs) do + local rhstype = rhs[i] and rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + lhs[i] = newobject(v,T.setter, rv,v.setter(r)) + elseif v:is "allocvar" then + v:settype(rhstype) + else + ensurelvalue(v) + end + end + return newobject(anchor,T.assignment,lhs,rhs) + else + --standard case #lhs == #rhs + --first take care of regular assignments + local regular, byfcall = assignmentkinds(lhs, rhs) + local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) + regular.rhs = insertcasts(anchor, vtypes, regular.rhs) + for i,v in ipairs(regular.lhs) do + local rhstype = regular.rhs[i] and regular.rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + regular.lhs[i] = newobject(v,T.setter, rv,v.setter(r)) + elseif v:is "allocvar" then + v:settype(rhstype) + else + ensurelvalue(v) + end + end + --take care of copy assignments using metamethods.__copy + local stmts = terralib.newlist() + for i,v in ipairs(byfcall.lhs) do + local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error + if v:is "setteru" then + local rv,r = allocvar(v,rhstype,"") + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], r)) + stmts:insert(newobject(v,T.setter, rv, v.setter(r))) + elseif v:is "allocvar" then + v:settype(rhstype) + stmts:insert(v) + local init = checkmetainit(anchor, v) + if init then + stmts:insert(init) + end + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + else + ensurelvalue(v) + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + end + end + if #stmts==0 then + --standard case, no meta-copy-assignments + return newobject(anchor,T.assignment, regular.lhs, regular.rhs) else - ensurelvalue(v) + --managed case using meta-copy-assignments + --the calls to `__copy` are in `stmts` + if #regular.lhs>0 then + stmts:insert(newobject(anchor,T.assignment, regular.lhs, regular.rhs)) + end + return createstatementlist(anchor, stmts) end end - return newobject(anchor,T.assignment,lhs,rhs) end local function checkmetadtors(anchor, stats) From 59d0945c6ee396425a9a10db3f72618cc37a1531 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:43:26 +0200 Subject: [PATCH 34/69] added simple testing of raii metamethods __init, __dtor, __copy, and a simple implementation of a unique smart pointer type with move semantics. --- tests/raii-unique_ptr.t | 95 +++++++++++++++++++++++++++++++++++ tests/raii.t | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 tests/raii-unique_ptr.t create mode 100644 tests/raii.t diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t new file mode 100644 index 000000000..4d41257ef --- /dev/null +++ b/tests/raii-unique_ptr.t @@ -0,0 +1,95 @@ +local std = {} +std.io = terralib.includec("stdio.h") +std.lib = terralib.includec("stdlib.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : &int + heap : bool +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: initializing object. start.\n") + self.data = nil -- initialize data pointer to nil + self.heap = false --flag to denote heap resource + std.io.printf("__init: initializing object. return.\n") +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor. start\n") + defer std.io.printf("__dtor: calling destructor. return\n") + if self.heap then + std.lib.free(self.data) + self.data = nil + self.heap = false + std.io.printf("__dtor: freed memory.\n") + end +end + +A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.metamethods.__copy:adddefinition( +terra(from : &A, to : &A) + std.io.printf("__copy: moving resources {&A, &A} -> {}.\n") + to.data = from.data + to.heap = from.heap + from.data = nil + from.heap = false +end) +A.metamethods.__copy:adddefinition( +terra(from : &int, to : &A) + std.io.printf("__copy: assignment {&int, &A} -> {}.\n") + to.data = from + to.heap = false --not known at compile time +end) + +--dereference ptr +terra A.methods.getvalue(self : &A) + return @self.data +end + +terra A.methods.setvalue(self : &A, value : int) + @self.data = value +end + +--heap memory allocation +terra A.methods.allocate(self : &A) + std.io.printf("allocate: allocating memory. start\n") + defer std.io.printf("allocate: allocating memory. return.\n") + self.data = [&int](std.lib.malloc(sizeof(int))) + self.heap = true +end + +terra testdereference() + var ptr : A + ptr:allocate() + ptr:setvalue(3) + return ptr:getvalue() +end + +terra returnheapresource() + var ptr : A + ptr:allocate() + ptr:setvalue(3) + return ptr +end + +terra testgetptr() + var ptr = returnheapresource() + return ptr:getvalue() +end + +local test = require "test" + +printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") +test.eq(testdereference(), 3) + +printtestheader("raii-unique_ptr.t: test return heap resource from function") +test.eq(testgetptr(), 3) + + diff --git a/tests/raii.t b/tests/raii.t new file mode 100644 index 000000000..4482f3df3 --- /dev/null +++ b/tests/raii.t @@ -0,0 +1,106 @@ +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = from.data + 10 +end) +A.metamethods.__copy:adddefinition(terra(from : int, to : &A) + std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") + to.data = from +end) +A.metamethods.__copy:adddefinition(terra(from : &A, to : &int) + std.io.printf("__copy: calling copy assignment {&A, &int} -> {}.\n") + @to = from.data +end) + + +terra testinit() + var a : A + return a.data +end + +terra testdtor() + var x : &int + do + var a : A + x = &a.data + end + return @x +end + +terra testcopyconstruction() + var a : A + var b = a + return b.data +end + +terra testcopyassignment() + var a : A + a.data = 2 + var b : A + b = a + return b.data +end + +terra testcopyassignment1() + var a : A + a = 3 + return a.data +end + +terra testcopyassignment2() + var a : A + var x : int + a.data = 5 + x = a + return x +end + +local test = require "test" + +--test if __init is called on object initialization to set 'a.data = 1' +printtestheader("raii.t - testing __init metamethod") +test.eq(testinit(), 1) + +--test if __dtor is called at the end of the scope to set 'a.data = -1' +printtestheader("raii.t - testing __dtor metamethod") +test.eq(testdtor(), -1) + +--test if __copy is called in construction 'var b = a' +printtestheader("raii.t - testing __copy metamethod in copy-construction") +test.eq(testcopyconstruction(), 11) + +--test if __copy is called in an assignment 'b = a' +printtestheader("raii.t - testing __copy metamethod in copy-assignment") +test.eq(testcopyassignment(), 12) + +--test if __copy is called in an assignment 'a = 3' +printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") +test.eq(testcopyassignment1(), 3) + +--test if __copy is called in an assignment 'x = a' for integer x +printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") +test.eq(testcopyassignment2(), 5) \ No newline at end of file From 69b96e01f8bfec26dbc7e6028b5d5d6df7e625c0 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:45:59 +0200 Subject: [PATCH 35/69] revert commented out code to make things work on my local machine. --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index d2e1d6f6b..9f1949573 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3692,11 +3692,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 5510a1d642b13a590075f0d37b6252cf917906c0 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 23:30:37 +0200 Subject: [PATCH 36/69] now calling __dtor before a new copy-assignment --- src/terralib.lua | 119 +++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 9f1949573..7e33095b7 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2785,6 +2785,61 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stmts end + --check if a __dtor metamethod is implemented for the type corresponding to `sym` + local function checkmetadtor(anchor, reciever) + if reciever.type and reciever.type:isstruct() then + if reciever.type.metamethods.__dtor then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + end + return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + end + end + end + + local function checkmetadtors(anchor, stats) + --extract the return statement from `stats`, if there is one + local function extractreturnstat() + local n = #stats + if n>0 then + local s = stats[n] + if s:is "returnstat" then + return s + end + end + end + local rstat = extractreturnstat() + --extract the returned `var` symbols from a return statement + local function extractreturnedsymbols() + local ret = {} + --loop over expressions in a `letin` return statement + for i,v in ipairs(rstat.expression.expressions) do + if v:is "var" then + ret[v.name] = v.symbol + end + end + return ret + end + + --get symbols that are returned in case of a return statement + local rsyms = rstat and extractreturnedsymbols() or {} + --get position at which to add destructor statements + local pos = rstat and #stats or #stats+1 + for name,sym in pairs(env:localenv()) do + --if not a return variable ckeck for an implementation of metamethods.__dtor + if not rsyms[name] then + local reciever = newobject(anchor,T.var, name, sym):setlvalue(true):withtype(sym.type) + local dtor = checkmetadtor(anchor, reciever) + if dtor then + --add deferred calls to the destructors + table.insert(stats, pos, newobject(anchor, T.defer, dtor)) + pos = pos + 1 + end + end + end + return stats + end + local function hasmetacopyassignment(typ) if typ and typ:isstruct() and typ.metamethods.__copy then return true @@ -3337,6 +3392,10 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) + local dtor = checkmetadtor(anchor, v) + if dtor then + stmts:insert(dtor) + end stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end end @@ -3354,56 +3413,6 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end - local function checkmetadtors(anchor, stats) - --extract the return statement from `stats`, if there is one - local function extractreturnstat() - local n = #stats - if n>0 then - local s = stats[n] - if s:is "returnstat" then - return s - end - end - end - local rstat = extractreturnstat() - --extract the returned `var` symbols from a return statement - local function extractreturnedsymbols() - local ret = {} - --loop over expressions in a `letin` return statement - for i,v in ipairs(rstat.expression.expressions) do - if v:is "var" then - ret[v.name] = v.symbol - end - end - return ret - end - --check if a __dtor metamethod is implemented for the type corresponding to `sym` - local function checkdtor(name,sym) - local mt = sym.type.metamethods - if mt and mt.__dtor then - local reciever = newobject(anchor, T.var, name, sym):setlvalue(true):withtype(sym.type) - return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") - end - end - - --get symbols that are returned in case of a return statement - local rsyms = rstat and extractreturnedsymbols() or {} - --get position at which to add destructor statements - local pos = rstat and #stats or #stats+1 - for name,sym in pairs(env:localenv()) do - --if not a return variable ckeck for an implementation of metamethods.__dtor - if not rsyms[name] then - local dtor = checkdtor(name,sym) - if dtor then - --add deferred calls to the destructors - table.insert(stats, pos, newobject(anchor, T.defer, dtor)) - pos = pos + 1 - end - end - end - return stats - end - function checkblock(s) env:enterblock() local stats = checkmetadtors(s, checkstmts(s.statements)) @@ -3692,11 +3701,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 010da09ba28d99c773582dc4eaba0e0d948c2315 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 21 May 2024 14:13:27 +0200 Subject: [PATCH 37/69] fixed dispatch of __copy. calling __copy is limited to rhs being a 'var', 'literal' or 'constant'. --- src/terralib.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7e33095b7..fdae97bdc 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2840,8 +2840,8 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stats end - local function hasmetacopyassignment(typ) - if typ and typ:isstruct() and typ.metamethods.__copy then + local function hasmetacopyassignment(reciever) + if reciever.type and reciever.type:isstruct() and reciever.type.metamethods.__copy then return true end return false @@ -2849,7 +2849,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local function checkmetacopyassignment(anchor, from, to) --if neither `from` or `to` are a struct then return - if not (hasmetacopyassignment(from.type) or hasmetacopyassignment(to.type)) then + if not (hasmetacopyassignment(from) or hasmetacopyassignment(to)) then return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -2861,7 +2861,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --list of overloaded __copy metamethods local overloads = terra.newlist() local function checkoverload(v) - if hasmetacopyassignment(v.type) then + if hasmetacopyassignment(v) then overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) end end @@ -3304,9 +3304,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - local rhstype = rhs[i] and rhs[i].type - local lhstype = lhs[i].type - if rhstype and (hasmetacopyassignment(lhstype) or hasmetacopyassignment(rhstype)) then + if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetacopyassignment(lhs[i]) or hasmetacopyassignment(rhs[i])) then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) From 4e363b02a8e434c42faee98f784007b7f16f5e2e Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 21 May 2024 17:38:08 +0200 Subject: [PATCH 38/69] added hasmetamethod(v, method). changed copy assignment behavior: deferred destructor call to data of lhs is performed after copy assignment. --- src/terralib.lua | 52 ++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index fdae97bdc..b94a5b703 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2762,6 +2762,15 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkcall(anchor, terra.newlist { fnlike }, fnargs, "first", false, location) end + --check if metamethod is implemented + local function hasmetamethod(v, method) + local typ = v.type + if typ and typ:isstruct() and typ.metamethods[method] then + return true + end + return false + end + --check if metamethods.__init is implemented local function checkmetainit(anchor, reciever) if reciever.type and reciever.type:isstruct() then @@ -2840,16 +2849,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return stats end - local function hasmetacopyassignment(reciever) - if reciever.type and reciever.type:isstruct() and reciever.type.metamethods.__copy then - return true - end - return false - end - local function checkmetacopyassignment(anchor, from, to) --if neither `from` or `to` are a struct then return - if not (hasmetacopyassignment(from) or hasmetacopyassignment(to)) then + if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -2861,7 +2863,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --list of overloaded __copy metamethods local overloads = terra.newlist() local function checkoverload(v) - if hasmetacopyassignment(v) then + if hasmetamethod(v, "__copy") then overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) end end @@ -3304,7 +3306,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetacopyassignment(lhs[i]) or hasmetacopyassignment(rhs[i])) then + if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetamethod(lhs[i],"__copy") or hasmetamethod(rhs[i],"__copy")) then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) @@ -3357,6 +3359,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(anchor,T.assignment,lhs,rhs) else --standard case #lhs == #rhs + local stmts = terralib.newlist() --first take care of regular assignments local regular, byfcall = assignmentkinds(lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) @@ -3370,10 +3373,17 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) v:settype(rhstype) else ensurelvalue(v) + --if 'v' is a managed variable then first assign 'v' to a temporary variable 'var tmp = v' + --then do the reassignment, 'v = ...', and then, call the destructor on 'tmp', freeing possible + --heap resources + if hasmetamethod(v, "__dtor") then + local tmpv, tmp = allocvar(v, v.type,"") + stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) + stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) + end end end --take care of copy assignments using metamethods.__copy - local stmts = terralib.newlist() for i,v in ipairs(byfcall.lhs) do local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error if v:is "setteru" then @@ -3390,9 +3400,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) - local dtor = checkmetadtor(anchor, v) - if dtor then - stmts:insert(dtor) + --first assign 'v' to a temporary variable 'var tmp = v' (shallow copy) + --then do the reassignment, 'v = ...', and then call the destructor + --on 'tmp', freeing possible heap resources + if hasmetamethod(v, "__dtor") then + local tmpv, tmp = allocvar(v, v.type,"") + stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) + stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) end stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end @@ -3699,11 +3713,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 3dbede1d35ea3223960561df09e76b7a16746628 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 16:04:59 +0200 Subject: [PATCH 39/69] fixed small bug in copyconstruction where the type is passed, e.g 'var a : A = ...', by setting rhs type only when required. --- src/terralib.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b94a5b703..b17c95320 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2856,8 +2856,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end --if `to` is an allocvar then set type and turn into corresponding `var` if to:is "allocvar" then - local typ = from.type or terra.types.error - to:settype(typ) + if not to.type then + to:settype(from.type or terra.types.error) + end to = newobject(anchor,T.var,to.name,to.symbol):setlvalue(true):withtype(to.type) end --list of overloaded __copy metamethods @@ -3391,7 +3392,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], r)) stmts:insert(newobject(v,T.setter, rv, v.setter(r))) elseif v:is "allocvar" then - v:settype(rhstype) + if not v.type then + v:settype(rhstype) + end stmts:insert(v) local init = checkmetainit(anchor, v) if init then From 2c83bf91d95687e374133680c09238bc3023cb9b Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 16:36:43 +0200 Subject: [PATCH 40/69] tests/raii-copyctr-cast.t, which combines __cast and __copy metamethods. --- tests/raii-copyctr-cast.t | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/raii-copyctr-cast.t diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t new file mode 100644 index 000000000..325e3b6e7 --- /dev/null +++ b/tests/raii-copyctr-cast.t @@ -0,0 +1,88 @@ +--[[ + We need that direct initialization + var a : A = b + yields the same result as + var a : A + a = b + If 'b' is a variable or a literal (something with a value) and the user has + implemented the right copy-assignment 'A.metamethods.__copy' then the copy + should be perform using this metamethod. + If the metamethod is not implemented for the exact types then a (user-defined) + implicit cast should be attempted. +--]] + +local test = require("test") +local std = {} +std.io = terralib.includec("stdio.h") + +struct A{ + data : int +} + +A.metamethods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.metamethods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.metamethods.__copy = terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = to.data + from.data + 10 +end + +A.metamethods.__cast = function(from, to, exp) + print("attempting cast from "..tostring(from).." --> "..tostring(to)) + if to == &A and from:ispointer() then + return quote + var tmp = A{@exp} + in + &tmp + end + end +end + +--[[ + The integer '2' will first be cast to a temporary of type A using + the user defined A.metamethods.__cast method. Then the metamethod + A.metamethods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.metamethods.__copy(from : &A, to : &A) + will be called to finalize the copy-construction. +--]] +terra testwithcast() + var a : A = 2 + return a.data +end + +-- to.data + from.data + 10 = 1 + 2 + 10 = 13 +test.eq(testwithcast(), 13) + + +A.metamethods.__copy = terralib.overloadedfunction("__copy") + +A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) + std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = to.data + from.data + 10 +end) + +A.metamethods.__copy:adddefinition(terra(from : int, to : &A) + std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") + to.data = to.data + from + 11 +end) + + +--[[ + The metamethod A.metamethods.__init(self : &A) is called to initialize + the variable and then the copy-constructor A.metamethods.__copy(from : int, to : &A) + will be called to finalize the copy-construction. +--]] +terra testwithoutcast() + var a : A = 2 + return a.data +end + +-- to.data + from.data + 10 = 1 + 2 + 11 = 14 +test.eq(testwithoutcast(), 14) \ No newline at end of file From 5e55ed58f21cfab3beb6abf69a79e5e0ac85e05c Mon Sep 17 00:00:00 2001 From: renehiemstra <152627545+renehiemstra@users.noreply.github.com> Date: Wed, 22 May 2024 08:11:37 +0200 Subject: [PATCH 41/69] forgot to uncomment SDKROOT code on macos --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b17c95320..598344faa 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3716,11 +3716,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 2c24f11c380be5e9f8c25d565be7157124e5ed59 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 22 May 2024 22:04:11 +0200 Subject: [PATCH 42/69] raii-shared_ptr.t which tests some functionality for a shared pointer-like type. --- tests/raii-shared_ptr.t | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/raii-shared_ptr.t diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t new file mode 100644 index 000000000..8317620e7 --- /dev/null +++ b/tests/raii-shared_ptr.t @@ -0,0 +1,148 @@ +local test = require("test") + +local std = {} +std.io = terralib.includec("stdio.h") +std.lib = terralib.includec("stdlib.h") + +local function printtestdescription(s) + print() + print("======================================") + print(s) + print("======================================") +end + +--implementation of a smart (shared) pointer type +local function SharedPtr(T) + + local struct A{ + data : &T --underlying data ptr (reference counter is stored in its head) + } + + --table for static methods + local static_methods = {} + + A.metamethods.__getmethod = function(self, methodname) + return A.methods[methodname] or static_methods[methodname] or error("No method " .. methodname .. "defined on " .. self) + end + + 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 + + A.methods.__dereference = terra(self : &A) + return @self.data + end + + static_methods.new = terra() + std.io.printf("new: allocating memory. start\n") + defer std.io.printf("new: allocating memory. return.\n") + --heap allocation for `data` with the reference counter `refcount` stored in its head and the real data in its tail + var head = sizeof(int8) + var tail = sizeof(T) + var ptr = [&int8](std.lib.malloc(head+tail)) + --assign to data + var x = A{[&T](ptr+1)} + --initializing the reference counter to one + @x:refcounter() = 1 + return x + end + + --initialization of pointer + 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 + + --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`. + if @self:refcounter() == 1 then + --free memory if the last shared pointer obj runs out of life + 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:refcounter()) + self.data = nil --reinitialize data ptr + else + --otherwise reduce reference counter + self:decreaserefcounter() + 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(from : &A, to : &A) + std.io.printf("__copy: calling copy-assignment operator. start\n") + defer std.io.printf("__copy: calling copy-assignment operator. return\n") + to.data = from.data + to:increaserefcounter() + end + + --return parameterized shared pointer type + return A +end + +local shared_ptr_int = SharedPtr(int) + +--testing vardef and copy assign +local terra test0() + var a : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 10 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --10 * 2 + end +end + +--testing var and copy assign +local terra test1() + var a : shared_ptr_int, b : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 11 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --11 * 2 + end +end + +printtestdescription("shared_ptr - copy construction.") +test.eq(test0(), 20) + +printtestdescription("shared_ptr - copy assignment.") +test.eq(test1(), 22) \ No newline at end of file From 543bac568397a5ec2f97794f029e550bb98fd28a Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 23 May 2024 20:25:10 +0200 Subject: [PATCH 43/69] enabled copy assignments for rhs 'select' variables and rhs pointer variables. --- src/terralib.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 598344faa..b48103f3a 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3307,7 +3307,21 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do - if rhs[i] and (rhs[i]:is "var" or rhs[i]:is "literal" or rhs[i]:is "constant") and (hasmetamethod(lhs[i],"__copy") or hasmetamethod(rhs[i],"__copy")) then + local cpassign = false + local r = rhs[i] + if r then + --alternatively, work on the r.type and check for + --r.type:isprimitive(), r.type:isstruct(), etc + if r:is "operator" and r.operator == "&" then + r = r.operands[1] + end + if r:is "var" or r:is "literal" or r:is "constant" or r:is "select" then + if hasmetamethod(lhs[i],"__copy") or hasmetamethod(r,"__copy") then + cpassign = true + end + end + end + if cpassign then --add assignment by __copy call byfcall.lhs:insert(lhs[i]) byfcall.rhs:insert(rhs[i]) From 1b56cf18068d1a52ba948939fc0f905a2dcef516 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 23 May 2024 20:26:34 +0200 Subject: [PATCH 44/69] raii-offset_ptr.t: example with a type that has some sematics of an offset pointer type, which has an overloaded __copy method. --- tests/raii-offset_ptr.t | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/raii-offset_ptr.t diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t new file mode 100644 index 000000000..71dc2279d --- /dev/null +++ b/tests/raii-offset_ptr.t @@ -0,0 +1,35 @@ +local test = require("test") + +local std = {} +std.io = terralib.includec("stdio.h") + +struct offset_ptr{ + offset : int + init : bool +} + +offset_ptr.metamethods.__copy = terra(from : &int64, to : &offset_ptr) + to.offset = [&int8](from) - [&int8](to) + to.init = true + std.io.printf("offset_ptr: __copy &int -> &offset_ptr\n") +end + +local struct A{ + integer_1 : int64 + integer_2 : int64 + ptr : offset_ptr +} + +terra test0() + var a : A + a.ptr = &a.integer_1 + var save_1 = a.ptr.offset + std.io.printf("value of a.ptr.offset: %d\n", a.ptr.offset) + a.ptr = &a.integer_2 + var save_2 = a.ptr.offset + std.io.printf("value of a.ptr.offset: %d\n", a.ptr.offset) + return save_1, save_2 +end + +--test the offset in bytes between ptr and the integers in struct A +test.meq({-16, -8},test0()) \ No newline at end of file From 9762db864db3ec01f50e4e5dd34eaaeb0f31953c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Sun, 26 May 2024 15:29:01 +0200 Subject: [PATCH 45/69] added lib/terralibext.t that is being called from terralib to enable composable raii datastructures. Missing __init, __copy, __dtor methods are generated on the fly when needed. --- lib/terralibext.t | 214 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 lib/terralibext.t diff --git a/lib/terralibext.t b/lib/terralibext.t new file mode 100644 index 000000000..e60fbf2c0 --- /dev/null +++ b/lib/terralibext.t @@ -0,0 +1,214 @@ +local std = { + io = terralib.includec("stdio.h") +} + +local function ondemand(fn) + local method + return macro(function(self,...) + if not method then + method = fn() + end + local args = {...} + return `method(&self,[args]) + end) +end + +local function addmissinginit(T) + + local generate = false + + local runinit = macro(function(self) + local T = self:gettype() + --avoid generating code for empty array initializers + local function hasinit(T) + if T:isstruct() then return T.methods.__init + elseif T:isarray() then return hasinit(T.type) + else return false end + end + if T:isstruct() then + if not T.methods.__init then + addmissinginit(T) + end + if T.methods.__init then + return quote + self:__init() + end + end + elseif T:isarray() and hasinit(T) then + return quote + var pa = &self + for i = 0,T.N do + runinit((@pa)[i]) + end + end + elseif T:ispointer() then + return quote + self = nil + end + end + return quote end + end) + + local generateinit = macro(function(self) + local T = self:gettype() + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + if e.field then + local expr = `runinit(self.[e.field]) + if #expr.tree.statements > 0 then + generate = true + end + stmts:insert( + expr + ) + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__init then + local method = terra(self : &T) + std.io.printf("%s:__init - default\n", [tostring(T)]) + generateinit(@self) + end + if generate then + T.methods.__init = method + end + end +end + + +local function addmissingdtor(T) + + local generate = false + + local rundtor = macro(function(self) + local T = self:gettype() + --avoid generating code for empty array initializers + local function hasdtor(T) + if T:isstruct() then return T.methods.__dtor + elseif T:isarray() then return hasdtor(T.type) + else return false end + end + if T:isstruct() then + if not T.methods.__dtor then + addmissingdtor(T) + end + if T.methods.__dtor then + return quote + self:__dtor() + end + end + elseif T:isarray() and hasdtor(T) then + return quote + var pa = &self + for i = 0,T.N do + rundtor((@pa)[i]) + end + end + end + return quote end + end) + + local generatedtor = macro(function(self) + local T = self:gettype() + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + if e.field then + local expr = `rundtor(self.[e.field]) + if #expr.tree.statements > 0 then + generate = true + stmts:insert(expr) + end + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__dtor then + local method = terra(self : &T) + std.io.printf("%s:__dtor - default\n", [tostring(T)]) + generatedtor(@self) + end + if generate then + T.methods.__dtor = method + end + end +end + +local function addmissingcopy(T) + + local generate = false + + local runcopy = macro(function(from, to) + local V = from:gettype() + --avoid generating code for empty array initializers + local function hascopy(U) + if U:isstruct() then return U.methods.__copy + elseif U:isarray() then return hascopy(U.type) + else return false end + end + if V:isstruct() then + if not V.methods.__copy then + addmissingcopy(V) + end + local method = V.methods.__copy + if method then + generate = true + return quote + method(&from, &to) + end + else + return quote + to = from + end + end + elseif V:isarray() and hasdtor(V) then + return quote + var pa = &self + for i = 0,V.N do + rundtor((@pa)[i]) + end + end + else + return quote + to = from + end + end + return quote end + end) + + local generatecopy = macro(function(from, to) + local stmts = terralib.newlist() + local entries = T:getentries() + for i,e in ipairs(entries) do + local field = e.field + local expr = `runcopy(from.[field], to.[field]) + print(expr) + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) + end + end + return stmts + end) + + if T:isstruct() and not T.methods.__copy then + local method = terra(from : &T, to : &T) + std.io.printf("%s:__copy - default\n", [tostring(T)]) + generatecopy(@from, @to) + end + if generate then + T.methods.__copy = method + end + end +end + +terralib.ext = { + addmissing = { + __init = addmissinginit, + __dtor = addmissingdtor, + __copy = addmissingcopy + } +} \ No newline at end of file From 44fa7ad978d0ea4e93140fb5b1c8d57aad3c4319 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Sun, 26 May 2024 20:02:42 +0200 Subject: [PATCH 46/69] changed __init, __copy, __dtor from metamethods to regular methods, such that they can potentially be called in sourcecode. --- src/terralib.lua | 18 +++++++++--------- tests/raii-copyctr-cast.t | 12 ++++++------ tests/raii-offset_ptr.t | 2 +- tests/raii-shared_ptr.t | 6 +++--- tests/raii-unique_ptr.t | 10 +++++----- tests/raii.t | 12 ++++++------ 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index b48103f3a..0c070f9fa 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2765,20 +2765,20 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if metamethod is implemented local function hasmetamethod(v, method) local typ = v.type - if typ and typ:isstruct() and typ.metamethods[method] then + if typ and typ:isstruct() and typ.methods[method] then return true end return false end - --check if metamethods.__init is implemented + --check if methods.__init is implemented local function checkmetainit(anchor, reciever) if reciever.type and reciever.type:isstruct() then if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - if reciever.type.metamethods.__init then - return checkmethodwithreciever(anchor, true, "__init", reciever, terralib.newlist(), "statement") + if reciever.type.methods.__init then + return checkmethodwithreciever(anchor, false, "__init", reciever, terralib.newlist(), "statement") end end end @@ -2797,11 +2797,11 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if a __dtor metamethod is implemented for the type corresponding to `sym` local function checkmetadtor(anchor, reciever) if reciever.type and reciever.type:isstruct() then - if reciever.type.metamethods.__dtor then + if reciever.type.methods.__dtor then if reciever:is "allocvar" then reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) end - return checkmethodwithreciever(anchor, true, "__dtor", reciever, terralib.newlist(), "statement") + return checkmethodwithreciever(anchor, false, "__dtor", reciever, terralib.newlist(), "statement") end end end @@ -2835,7 +2835,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --get position at which to add destructor statements local pos = rstat and #stats or #stats+1 for name,sym in pairs(env:localenv()) do - --if not a return variable ckeck for an implementation of metamethods.__dtor + --if not a return variable ckeck for an implementation of methods.__dtor if not rsyms[name] then local reciever = newobject(anchor,T.var, name, sym):setlvalue(true):withtype(sym.type) local dtor = checkmetadtor(anchor, reciever) @@ -2865,7 +2865,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local overloads = terra.newlist() local function checkoverload(v) if hasmetamethod(v, "__copy") then - overloads:insert(asterraexpression(anchor, v.type.metamethods.__copy, "luaobject")) + overloads:insert(asterraexpression(anchor, v.type.methods.__copy, "luaobject")) end end --add overloaded methods based on left- and right-hand-side of the assignment @@ -3398,7 +3398,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end end - --take care of copy assignments using metamethods.__copy + --take care of copy assignments using methods.__copy for i,v in ipairs(byfcall.lhs) do local rhstype = byfcall.rhs[i] and byfcall.rhs[i].type or terra.types.error if v:is "setteru" then diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 325e3b6e7..5a14389fa 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -19,17 +19,17 @@ struct A{ data : int } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: calling initializer.\n") self.data = 1 end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor.\n") self.data = -1 end -A.metamethods.__copy = terra(from : &A, to : &A) +A.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = to.data + from.data + 10 end @@ -61,14 +61,14 @@ end test.eq(testwithcast(), 13) -A.metamethods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) +A.methods.__copy:adddefinition(terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = to.data + from.data + 10 end) -A.metamethods.__copy:adddefinition(terra(from : int, to : &A) +A.methods.__copy:adddefinition(terra(from : int, to : &A) std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") to.data = to.data + from + 11 end) diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index 71dc2279d..72ba0d10c 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -8,7 +8,7 @@ struct offset_ptr{ init : bool } -offset_ptr.metamethods.__copy = terra(from : &int64, to : &offset_ptr) +offset_ptr.methods.__copy = terra(from : &int64, to : &offset_ptr) to.offset = [&int8](from) - [&int8](to) to.init = true std.io.printf("offset_ptr: __copy &int -> &offset_ptr\n") diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index 8317620e7..2802fc9ed 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -65,14 +65,14 @@ local function SharedPtr(T) end --initialization of pointer - A.metamethods.__init = terra(self : &A) + A.methods.__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 --destructor - A.metamethods.__dtor = terra(self : &A) + A.methods.__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 @@ -96,7 +96,7 @@ local function SharedPtr(T) --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(from : &A, to : &A) + A.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy-assignment operator. start\n") defer std.io.printf("__copy: calling copy-assignment operator. return\n") to.data = from.data diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 4d41257ef..782da16eb 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -14,14 +14,14 @@ struct A{ heap : bool } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: initializing object. start.\n") self.data = nil -- initialize data pointer to nil self.heap = false --flag to denote heap resource std.io.printf("__init: initializing object. return.\n") end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor. start\n") defer std.io.printf("__dtor: calling destructor. return\n") if self.heap then @@ -32,8 +32,8 @@ A.metamethods.__dtor = terra(self : &A) end end -A.metamethods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition( +A.methods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy:adddefinition( terra(from : &A, to : &A) std.io.printf("__copy: moving resources {&A, &A} -> {}.\n") to.data = from.data @@ -41,7 +41,7 @@ terra(from : &A, to : &A) from.data = nil from.heap = false end) -A.metamethods.__copy:adddefinition( +A.methods.__copy:adddefinition( terra(from : &int, to : &A) std.io.printf("__copy: assignment {&int, &A} -> {}.\n") to.data = from diff --git a/tests/raii.t b/tests/raii.t index 4482f3df3..f59795393 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -12,26 +12,26 @@ struct A{ data : int } -A.metamethods.__init = terra(self : &A) +A.methods.__init = terra(self : &A) std.io.printf("__init: calling initializer.\n") self.data = 1 end -A.metamethods.__dtor = terra(self : &A) +A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor.\n") self.data = -1 end -A.metamethods.__copy = terralib.overloadedfunction("__copy") -A.metamethods.__copy:adddefinition(terra(from : &A, to : &A) +A.methods.__copy = terralib.overloadedfunction("__copy") +A.methods.__copy:adddefinition(terra(from : &A, to : &A) std.io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") to.data = from.data + 10 end) -A.metamethods.__copy:adddefinition(terra(from : int, to : &A) +A.methods.__copy:adddefinition(terra(from : int, to : &A) std.io.printf("__copy: calling copy assignment {int, &A} -> {}.\n") to.data = from end) -A.metamethods.__copy:adddefinition(terra(from : &A, to : &int) +A.methods.__copy:adddefinition(terra(from : &A, to : &int) std.io.printf("__copy: calling copy assignment {&A, &int} -> {}.\n") @to = from.data end) From a2671e30126803b70f870b0a9c04b60c8f93934c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:49:44 +0200 Subject: [PATCH 47/69] cleaned up terralibext.t --- lib/terralibext.t | 97 +++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index e60fbf2c0..0ff0d754e 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -2,46 +2,39 @@ local std = { io = terralib.includec("stdio.h") } -local function ondemand(fn) - local method - return macro(function(self,...) - if not method then - method = fn() - end - local args = {...} - return `method(&self,[args]) - end) -end - local function addmissinginit(T) + --flag that signals that a missing __init method needs to + --be generated local generate = false local runinit = macro(function(self) - local T = self:gettype() + local V = self:gettype() --avoid generating code for empty array initializers - local function hasinit(T) - if T:isstruct() then return T.methods.__init - elseif T:isarray() then return hasinit(T.type) + local function hasinit(U) + if U:isstruct() then return U.methods.__init + elseif U:isarray() then return hasinit(U.type) else return false end end - if T:isstruct() then - if not T.methods.__init then - addmissinginit(T) + if V:isstruct() then + if not V.methods.__init then + addmissinginit(V) end - if T.methods.__init then + local method = V.methods.__init + if method then + generate = true return quote self:__init() end end - elseif T:isarray() and hasinit(T) then + elseif V:isarray() and hasinit(V) then return quote var pa = &self for i = 0,T.N do runinit((@pa)[i]) end end - elseif T:ispointer() then + elseif V:ispointer() then return quote self = nil end @@ -56,12 +49,9 @@ local function addmissinginit(T) for i,e in ipairs(entries) do if e.field then local expr = `runinit(self.[e.field]) - if #expr.tree.statements > 0 then - generate = true + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) end - stmts:insert( - expr - ) end end return stmts @@ -69,7 +59,7 @@ local function addmissinginit(T) if T:isstruct() and not T.methods.__init then local method = terra(self : &T) - std.io.printf("%s:__init - default\n", [tostring(T)]) + std.io.printf("%s:__init - generated\n", [tostring(T)]) generateinit(@self) end if generate then @@ -81,26 +71,30 @@ end local function addmissingdtor(T) + --flag that signals that a missing __dtor method needs to + --be generated local generate = false local rundtor = macro(function(self) - local T = self:gettype() - --avoid generating code for empty array initializers - local function hasdtor(T) - if T:isstruct() then return T.methods.__dtor - elseif T:isarray() then return hasdtor(T.type) + local V = self:gettype() + --avoid generating code for empty array destructors + local function hasdtor(U) + if U:isstruct() then return U.methods.__dtor + elseif U:isarray() then return hasdtor(U.type) else return false end end - if T:isstruct() then - if not T.methods.__dtor then - addmissingdtor(T) + if V:isstruct() then + if not V.methods.__dtor then + addmissingdtor(V) end - if T.methods.__dtor then + local method = V.methods.__dtor + if method then + generate = true return quote self:__dtor() end end - elseif T:isarray() and hasdtor(T) then + elseif V:isarray() and hasdtor(V) then return quote var pa = &self for i = 0,T.N do @@ -118,8 +112,7 @@ local function addmissingdtor(T) for i,e in ipairs(entries) do if e.field then local expr = `rundtor(self.[e.field]) - if #expr.tree.statements > 0 then - generate = true + if expr and #expr.tree.statements > 0 then stmts:insert(expr) end end @@ -129,7 +122,7 @@ local function addmissingdtor(T) if T:isstruct() and not T.methods.__dtor then local method = terra(self : &T) - std.io.printf("%s:__dtor - default\n", [tostring(T)]) + std.io.printf("%s:__dtor - generated\n", [tostring(T)]) generatedtor(@self) end if generate then @@ -140,17 +133,20 @@ end local function addmissingcopy(T) + --flag that signals that a missing __copy method needs to + --be generated local generate = false local runcopy = macro(function(from, to) - local V = from:gettype() + local U = from:gettype() + local V = to:gettype() --avoid generating code for empty array initializers - local function hascopy(U) - if U:isstruct() then return U.methods.__copy - elseif U:isarray() then return hascopy(U.type) + local function hascopy(W) + if W:isstruct() then return W.methods.__copy + elseif W:isarray() then return hascopy(W.type) else return false end end - if V:isstruct() then + if V:isstruct() and U==V then if not V.methods.__copy then addmissingcopy(V) end @@ -185,10 +181,11 @@ local function addmissingcopy(T) local entries = T:getentries() for i,e in ipairs(entries) do local field = e.field - local expr = `runcopy(from.[field], to.[field]) - print(expr) - if expr and #expr.tree.statements > 0 then - stmts:insert(expr) + if field then + local expr = `runcopy(from.[field], to.[field]) + if expr and #expr.tree.statements > 0 then + stmts:insert(expr) + end end end return stmts @@ -196,7 +193,7 @@ local function addmissingcopy(T) if T:isstruct() and not T.methods.__copy then local method = terra(from : &T, to : &T) - std.io.printf("%s:__copy - default\n", [tostring(T)]) + std.io.printf("%s:__copy - generate\n", [tostring(T)]) generatecopy(@from, @to) end if generate then From 0d21b6d91f9fc2e2baca138b42fae38588213f82 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:51:23 +0200 Subject: [PATCH 48/69] updated tests to incorporate changes in 'terralibext.t' --- tests/raii-copyctr-cast.t | 2 ++ tests/raii-offset_ptr.t | 2 ++ tests/raii-shared_ptr.t | 44 ++++++++++----------------------------- tests/raii-unique_ptr.t | 2 ++ tests/raii.t | 3 +++ 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 5a14389fa..501efc404 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" --[[ We need that direct initialization var a : A = b diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index 72ba0d10c..e761d3ac7 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local test = require("test") local std = {} diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index 2802fc9ed..f018e118d 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local test = require("test") local std = {} @@ -64,14 +66,12 @@ local function SharedPtr(T) return x end - --initialization of pointer A.methods.__init = terra(self : &A) - std.io.printf("__init: initializing object. start.\n") + std.io.printf("__init: initializing object\n") self.data = nil -- initialize data pointer to nil std.io.printf("__init: initializing object. return.\n") end - --destructor A.methods.__dtor = terra(self : &A) std.io.printf("__dtor: calling destructor. start\n") defer std.io.printf("__dtor: calling destructor. return\n") @@ -93,9 +93,6 @@ local function SharedPtr(T) 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.methods.__copy = terra(from : &A, to : &A) std.io.printf("__copy: calling copy-assignment operator. start\n") defer std.io.printf("__copy: calling copy-assignment operator. return\n") @@ -109,40 +106,21 @@ end local shared_ptr_int = SharedPtr(int) ---testing vardef and copy assign +printtestdescription("shared_ptr - copy construction.") local terra test0() var a : shared_ptr_int - std.io.printf("main: a.refcount: %p\n", a:refcounter()) a = shared_ptr_int.new() - @a.data = 10 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()) - if a:refcounter()==b:refcounter() then - return @b.data * @a:refcounter() --10 * 2 - end -end - ---testing var and copy assign -local terra test1() - var a : shared_ptr_int, b : shared_ptr_int - std.io.printf("main: a.refcount: %p\n", a:refcounter()) - a = shared_ptr_int.new() - @a.data = 11 + @a.data = 10 std.io.printf("main: a.data: %d\n", @a.data) std.io.printf("main: a.refcount: %d\n", @a:refcounter()) - b = a + var b = a std.io.printf("main: b.data: %d\n", @b.data) std.io.printf("main: a.refcount: %d\n", @a:refcounter()) - if a:refcounter()==b:refcounter() then - return @b.data * @a:refcounter() --11 * 2 - end + --if a:refcounter()==b:refcounter() then + -- return @b.data * @a:refcounter() --10 * 2 + --end end - -printtestdescription("shared_ptr - copy construction.") -test.eq(test0(), 20) - -printtestdescription("shared_ptr - copy assignment.") -test.eq(test1(), 22) \ No newline at end of file +test0() +--test.eq(test0(), 20) \ No newline at end of file diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 782da16eb..7609b868c 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -1,3 +1,5 @@ +--load 'terralibext' to enable raii +require "terralibext" local std = {} std.io = terralib.includec("stdio.h") std.lib = terralib.includec("stdlib.h") diff --git a/tests/raii.t b/tests/raii.t index f59795393..3ba51c6ef 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -1,3 +1,6 @@ +--load 'terralibext' to enable raii +require "terralibext" + local std = {} std.io = terralib.includec("stdio.h") From 84dfe04b05b52d296b5d2240828e5419f7434a4c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:52:13 +0200 Subject: [PATCH 49/69] raii-compose.t: testing composable use of managed structs. --- tests/raii-compose.t | 152 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tests/raii-compose.t diff --git a/tests/raii-compose.t b/tests/raii-compose.t new file mode 100644 index 000000000..07f1d33de --- /dev/null +++ b/tests/raii-compose.t @@ -0,0 +1,152 @@ +--load 'terralibext' to enable raii +require "terralibext" + +local test = require "test" + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +local std = { + io = terralib.includec("stdio.h") +} + +--A is a managed struct, as it implements __init, __copy, __dtor +local struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("A.__init\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("A.__dtor\n") + self.data = -1 +end + +A.methods.__copy = terra(from : &A, to : &A) + std.io.printf("A.__copy\n") + to.data = from.data + 2 +end + +local struct B{ + data : int +} + +local struct C{ + data_a : A --managed + data_b : B --not managed +} + +local struct D{ + data_a : A --managed + data_b : B --not managed + data_c : C +} + +printtestheader("raii-compose.t - testing __init for managed struct") +local terra testinit_A() + var a : A + return a.data +end +test.eq(testinit_A(), 1) + +printtestheader("raii-compose.t - testing __init for managed field") +local terra testinit_C() + var c : C + return c.data_a.data +end +test.eq(testinit_C(), 1) + +printtestheader("raii-compose.t - testing __init for managed field and subfield") +local terra testinit_D() + var d : D + return d.data_a.data + d.data_c.data_a.data +end +test.eq(testinit_D(), 2) + +printtestheader("raii-compose.t - testing __dtor for managed struct") +local terra testdtor_A() + var x : &int + do + var a : A + x = &a.data + end + return @x +end +test.eq(testdtor_A(), -1) + +printtestheader("raii-compose.t - testing __dtor for managed field") +local terra testdtor_C() + var x : &int + do + var c : C + x = &c.data_a.data + end + return @x +end +test.eq(testdtor_C(), -1) + +printtestheader("raii-compose.t - testing __dtor for managed field and subfield") +local terra testdtor_D() + var x : &int + var y : &int + do + var d : D + x = &d.data_a.data + y = &d.data_c.data_a.data + end + return @x + @y +end +test.eq(testdtor_D(), -2) + +printtestheader("raii-compose.t - testing __copy for managed field") +terra testcopyassignment_C() + var c_1 : C + var c_2 : C + c_1.data_a.data = 5 + c_2 = c_1 + std.io.printf("value c_2._data_a.data %d\n", c_2.data_a.data) + return c_2.data_a.data +end +test.eq(testcopyassignment_C(), 5 + 2) + +printtestheader("raii-compose.t - testing __copy for managed field and subfield") +terra testcopyassignment_D() + var d_1 : D + var d_2 : D + d_1.data_a.data = 5 + d_1.data_c.data_a.data = 6 + d_2 = d_1 + std.io.printf("value d_2._data_a.data %d\n", d_2.data_a.data) + std.io.printf("value d_2._data_c.data_a.data %d\n", d_2.data_c.data_a.data) + return d_2.data_a.data + d_2.data_c.data_a.data +end +test.eq(testcopyassignment_D(), 5 + 2 + 6 + 2) + +printtestheader("raii-compose.t - testing __copy construction for managed field") +terra testcopyconstruction_C() + var c_1 : C + c_1.data_a.data = 5 + var c_2 : C = c_1 + std.io.printf("value c_2._data_a.data %d\n", c_2.data_a.data) + return c_2.data_a.data +end +test.eq(testcopyconstruction_C(), 5 + 2) + +printtestheader("raii-compose.t - testing __copy construction for managed field and subfield") +terra testcopyconstruction_D() + var d_1 : D + d_1.data_a.data = 5 + d_1.data_c.data_a.data = 6 + var d_2 = d_1 + std.io.printf("value d_2._data_a.data %d\n", d_2.data_a.data) + std.io.printf("value d_2._data_c.data_a.data %d\n", d_2.data_c.data_a.data) + return d_2.data_a.data + d_2.data_c.data_a.data +end +test.eq(testcopyconstruction_D(), 5 + 2 + 6 + 2) From 433c5b778cad00cc476dba6a36404b109b41d188 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 14:53:34 +0200 Subject: [PATCH 50/69] first implementation of raii composable managed datastructures --- src/terralib.lua | 73 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 0c070f9fa..05e0cb2d4 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -2764,6 +2764,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if metamethod is implemented local function hasmetamethod(v, method) + if not terralib.ext then return false end local typ = v.type if typ and typ:isstruct() and typ.methods[method] then return true @@ -2773,17 +2774,24 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if methods.__init is implemented local function checkmetainit(anchor, reciever) - if reciever.type and reciever.type:isstruct() then - if reciever:is "allocvar" then - reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) - end - if reciever.type.methods.__init then + if not terralib.ext then return end + local typ = reciever.type + if typ and typ:isstruct() then + --try to add missing __init method + if not typ.methods.__init then + terralib.ext.addmissing.__init(typ) + end + if typ.methods.__init then + if reciever:is "allocvar" then + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(typ) + end return checkmethodwithreciever(anchor, false, "__init", reciever, terralib.newlist(), "statement") end end end local function checkmetainitializers(anchor, lhs) + if not terralib.ext then return end local stmts = terralib.newlist() for i,e in ipairs(lhs) do local init = checkmetainit(anchor, e) @@ -2796,10 +2804,16 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --check if a __dtor metamethod is implemented for the type corresponding to `sym` local function checkmetadtor(anchor, reciever) - if reciever.type and reciever.type:isstruct() then - if reciever.type.methods.__dtor then + if not terralib.ext then return end + local typ = reciever.type + if typ and typ:isstruct() then + --try to add missing __dtor method + if not typ.methods.__dtor then + terralib.ext.addmissing.__dtor(typ) + end + if typ.methods.__dtor then if reciever:is "allocvar" then - reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(reciever.type) + reciever = newobject(anchor,T.var,reciever.name,reciever.symbol):setlvalue(true):withtype(typ) end return checkmethodwithreciever(anchor, false, "__dtor", reciever, terralib.newlist(), "statement") end @@ -2807,6 +2821,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end local function checkmetadtors(anchor, stats) + if not terralib.ext then return stats end --extract the return statement from `stats`, if there is one local function extractreturnstat() local n = #stats @@ -2850,8 +2865,24 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end local function checkmetacopyassignment(anchor, from, to) - --if neither `from` or `to` are a struct then return - if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then + if not terralib.ext then return end + local ftype, ttype = from.type, to.type + if (ftype and ftype:isstruct()) or (ttype and ttype:isstruct()) then + --case of equal struct types + if ftype == ttype then + if not ftype.methods.__copy then + --try add missing __copy method + terralib.ext.addmissing.__copy(ftype) + end + --if __copy was unsuccessful return to do regular copy + if not (ftype.methods.__copy) then return end + else + --otherwise + if not (hasmetamethod(from, "__copy") or hasmetamethod(to, "__copy")) then return end + end + else + --only struct types are managed + --resort to regular copy return end --if `to` is an allocvar then set type and turn into corresponding `var` @@ -3303,11 +3334,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end --divide assignment into regular assignments and copy assignments - local function assignmentkinds(lhs, rhs) + local function assignmentkinds(anchor, lhs, rhs) local regular = {lhs = terralib.newlist(), rhs = terralib.newlist()} local byfcall = {lhs = terralib.newlist(), rhs = terralib.newlist()} for i=1,#lhs do local cpassign = false + --ToDo: for now we call 'checkmetacopyassignment' twice. Refactor with 'createassignment' local r = rhs[i] if r then --alternatively, work on the r.type and check for @@ -3316,7 +3348,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) r = r.operands[1] end if r:is "var" or r:is "literal" or r:is "constant" or r:is "select" then - if hasmetamethod(lhs[i],"__copy") or hasmetamethod(r,"__copy") then + if checkmetacopyassignment(anchor, r, lhs[i]) then cpassign = true end end @@ -3376,7 +3408,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --standard case #lhs == #rhs local stmts = terralib.newlist() --first take care of regular assignments - local regular, byfcall = assignmentkinds(lhs, rhs) + local regular, byfcall = assignmentkinds(anchor, lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) regular.rhs = insertcasts(anchor, vtypes, regular.rhs) for i,v in ipairs(regular.lhs) do @@ -3531,7 +3563,10 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return createassignment(s,lhs,rhs) else local res = createstatementlist(s,lhs) - res.statements:insertall(checkmetainitializers(s, lhs)) + local ini = checkmetainitializers(s, lhs) + if ini then + res.statements:insertall(ini) + end return res end elseif s:is "assignment" then @@ -3730,11 +3765,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert("-isysroot") + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From 93114ff1e5b08338e62e20efaff432e4dc8d65b1 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:36:41 +0200 Subject: [PATCH 51/69] updated tests/raii-shared_ptr.t --- tests/raii-shared_ptr.t | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/raii-shared_ptr.t b/tests/raii-shared_ptr.t index f018e118d..18ab3b56b 100644 --- a/tests/raii-shared_ptr.t +++ b/tests/raii-shared_ptr.t @@ -109,18 +109,34 @@ local shared_ptr_int = SharedPtr(int) printtestdescription("shared_ptr - copy construction.") local terra test0() var a : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) a = shared_ptr_int.new() - std.io.printf("main: a.data: %d\n", @a.data) - std.io.printf("main: a.refcount: %d\n", @a:refcounter()) @a.data = 10 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()) - --if a:refcounter()==b:refcounter() then - -- return @b.data * @a:refcounter() --10 * 2 - --end + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --10 * 2 + end +end +test.eq(test0(), 20) + +printtestdescription("shared_ptr - copy assignment.") +local terra test1() + var a : shared_ptr_int, b : shared_ptr_int + std.io.printf("main: a.refcount: %p\n", a:refcounter()) + a = shared_ptr_int.new() + @a.data = 11 + 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()) + if a:refcounter()==b:refcounter() then + return @b.data * @a:refcounter() --11 * 2 + end end -test0() ---test.eq(test0(), 20) \ No newline at end of file +test.eq(test1(), 22) + From 880342e1330e14330d7c600b246a5717426f1a21 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:38:20 +0200 Subject: [PATCH 52/69] fixed assignment__copy assignment. removed __dtor in custom __copy assignment call. resource handling is in the hands of the programmer in case of __copy. --- src/terralib.lua | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 05e0cb2d4..523cd0082 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3363,6 +3363,13 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) regular.rhs:insert(rhs[i]) end end + if #byfcall>0 and #byfcall+#regular>1 then + --__copy can potentially mutate left and right-handsides in an + --assignment. So we prohibit assignments that may involve something + --like a swap: u,v = v, u. + --for now we prohibit this by limiting such assignments + erroratlocation(anchor, "a custom __copy assignment is not supported for tuples.") + end return regular, byfcall end @@ -3407,6 +3414,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else --standard case #lhs == #rhs local stmts = terralib.newlist() + local post = terralib.newlist() --first take care of regular assignments local regular, byfcall = assignmentkinds(anchor, lhs, rhs) local vtypes = regular.lhs:map(function(v) return v.type or "passthrough" end) @@ -3420,13 +3428,19 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) v:settype(rhstype) else ensurelvalue(v) - --if 'v' is a managed variable then first assign 'v' to a temporary variable 'var tmp = v' - --then do the reassignment, 'v = ...', and then, call the destructor on 'tmp', freeing possible - --heap resources + --if 'v' is a managed variable then + --(1) var tmp_v : v.type = rhs[i] --evaluate the rhs and store in tmp_v + --(2) v:__dtor() --delete old v + --(3) v = tmp_v --copy new data to v + --these steps are needed because rhs[i] may be a function of v, and the assignment in 'anchor' + --could involve something like a swap: u, v = v, u if hasmetamethod(v, "__dtor") then - local tmpv, tmp = allocvar(v, v.type,"") - stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) - stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) + local tmpa_v, tmp_v = allocvar(v, v.type,"") + --define temporary variable as new left-hand-side + regular.lhs[i] = tmpa_v + --call v:__dtor() and set v = tmp_v + post:insert(checkmetadtor(anchor, v)) + post:insert(newobject(anchor,T.assignment, List{v}, List{tmp_v})) end end end @@ -3449,18 +3463,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) else ensurelvalue(v) - --first assign 'v' to a temporary variable 'var tmp = v' (shallow copy) - --then do the reassignment, 'v = ...', and then call the destructor - --on 'tmp', freeing possible heap resources - if hasmetamethod(v, "__dtor") then - local tmpv, tmp = allocvar(v, v.type,"") - stmts:insert(newobject(anchor,T.assignment, List{tmpv}, List{v})) - stmts:insert(newobject(anchor,T.defer, checkmetadtor(v, tmp))) - end + --apply copy assignment - memory resource management is in the + --hands of the programmer stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end end - if #stmts==0 then + if #stmts==0 and #post==0 then --standard case, no meta-copy-assignments return newobject(anchor,T.assignment, regular.lhs, regular.rhs) else @@ -3469,6 +3477,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if #regular.lhs>0 then stmts:insert(newobject(anchor,T.assignment, regular.lhs, regular.rhs)) end + stmts:insertall(post) return createstatementlist(anchor, stmts) end end From d2d2ce83612f4525d73081bc8ff3ac952ba45561 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 21:53:11 +0200 Subject: [PATCH 53/69] reorganized raii tests. --- tests/raii-copyctr-cast.t | 28 +++++++++++++--------------- tests/raii-offset_ptr.t | 8 ++++---- tests/raii-unique_ptr.t | 24 ++++++++++++------------ 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index 501efc404..ca31c9031 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -1,5 +1,4 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii --[[ We need that direct initialization var a : A = b @@ -7,15 +6,16 @@ require "terralibext" var a : A a = b If 'b' is a variable or a literal (something with a value) and the user has - implemented the right copy-assignment 'A.metamethods.__copy' then the copy - should be perform using this metamethod. - If the metamethod is not implemented for the exact types then a (user-defined) + implemented the right copy-assignment 'A.methods.__copy' then the copy + should be performed using this method. + If the method is not implemented for the exact types then a (user-defined) implicit cast should be attempted. --]] local test = require("test") -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} struct A{ data : int @@ -49,9 +49,9 @@ end --[[ The integer '2' will first be cast to a temporary of type A using - the user defined A.metamethods.__cast method. Then the metamethod - A.metamethods.__init(self : &A) is called to initialize the variable - and then the copy-constructor A.metamethods.__copy(from : &A, to : &A) + the user defined A.metamethods.__cast method. Then the method + A.methods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.methods.__copy(from : &A, to : &A) will be called to finalize the copy-construction. --]] terra testwithcast() @@ -75,16 +75,14 @@ A.methods.__copy:adddefinition(terra(from : int, to : &A) to.data = to.data + from + 11 end) - --[[ - The metamethod A.metamethods.__init(self : &A) is called to initialize - the variable and then the copy-constructor A.metamethods.__copy(from : int, to : &A) - will be called to finalize the copy-construction. + The method A.methods.__init(self : &A) is called to initialize the variable + and then the copy-constructor A.methods.__copy(from : int, to : &A) will be + called to finalize the copy-construction. --]] terra testwithoutcast() var a : A = 2 return a.data end - -- to.data + from.data + 10 = 1 + 2 + 11 = 14 test.eq(testwithoutcast(), 14) \ No newline at end of file diff --git a/tests/raii-offset_ptr.t b/tests/raii-offset_ptr.t index e761d3ac7..a06cdbf87 100644 --- a/tests/raii-offset_ptr.t +++ b/tests/raii-offset_ptr.t @@ -1,9 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii local test = require("test") -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} struct offset_ptr{ offset : int diff --git a/tests/raii-unique_ptr.t b/tests/raii-unique_ptr.t index 7609b868c..7275e5cb0 100644 --- a/tests/raii-unique_ptr.t +++ b/tests/raii-unique_ptr.t @@ -1,8 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" -local std = {} -std.io = terralib.includec("stdio.h") -std.lib = terralib.includec("stdlib.h") +require "terralibext" --load 'terralibext' to enable raii + +local std = { + io = terralib.includec("stdio.h"), + lib = terralib.includec("stdlib.h") +} local function printtestheader(s) print() @@ -67,12 +68,17 @@ terra A.methods.allocate(self : &A) self.heap = true end + +local test = require "test" + +printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") terra testdereference() var ptr : A ptr:allocate() ptr:setvalue(3) return ptr:getvalue() end +test.eq(testdereference(), 3) terra returnheapresource() var ptr : A @@ -81,17 +87,11 @@ terra returnheapresource() return ptr end +printtestheader("raii-unique_ptr.t: test return heap resource from function") terra testgetptr() var ptr = returnheapresource() return ptr:getvalue() end - -local test = require "test" - -printtestheader("raii-unique_ptr.t: test return ptr value from function before resource is deleted") -test.eq(testdereference(), 3) - -printtestheader("raii-unique_ptr.t: test return heap resource from function") test.eq(testgetptr(), 3) From 2c3a4e024e3c9db0a505db3f952a3ca76df0739c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 23:31:05 +0200 Subject: [PATCH 54/69] raii-tuple-default-copy.t and fails/raii-tuple-custom-copy.t --- tests/fails/raii-tuple-custom-copy.t | 42 ++++++++++++++++++++++++++ tests/raii-tuple-default-copy.t | 42 ++++++++++++++++++++++++++ tests/raii.t | 45 ++++++++++------------------ 3 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 tests/fails/raii-tuple-custom-copy.t create mode 100644 tests/raii-tuple-default-copy.t diff --git a/tests/fails/raii-tuple-custom-copy.t b/tests/fails/raii-tuple-custom-copy.t new file mode 100644 index 000000000..71a71a08e --- /dev/null +++ b/tests/fails/raii-tuple-custom-copy.t @@ -0,0 +1,42 @@ +require "terralibext" --load 'terralibext' to enable raii + +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.methods.__copy = terra(from : &A, to : &A) + std.io.printf("__copy: calling custom copy.\n") + to.data = from.data+1 +end + +printtestheader("raii.t - testing custom copy for tuples") +terra test0() + var a = A{1} + var b = A{2} + a, b = b, a + --tuple assignments are prohibited when __copy is implemented + --because proper resource management cannot be guaranteed + --(at least not yet) + return a.data, b.data +end +test0() \ No newline at end of file diff --git a/tests/raii-tuple-default-copy.t b/tests/raii-tuple-default-copy.t new file mode 100644 index 000000000..83a6eee6f --- /dev/null +++ b/tests/raii-tuple-default-copy.t @@ -0,0 +1,42 @@ +require "terralibext" --load 'terralibext' to enable raii +local test = require "test" + +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +printtestheader("raii.t - testing default copy metamethod") +terra test0() + var a = A{1} + var b = A{2} + a, b = b, a --bitcopies should work in a swap + --the following code is generated + --var tmp_a = __move(b) --store evaluated rhs in a tmp variable + --var tmp_b = __move(a) --store evaluated rhs in a tmp variable + --a:__dtor() --delete old memory (nothing happens as 'a' has been moved from) + --b:__dtor() --delete old memory (nothing happens as 'a' has been moved from) + --a = __move(tmp_a) --move new data into 'a' + --b = __move(tmp_b) --move new data into 'b' + return a.data, b.data +end +test.meq({2, 1}, test0()) \ No newline at end of file diff --git a/tests/raii.t b/tests/raii.t index 3ba51c6ef..96542c6ae 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -1,8 +1,9 @@ ---load 'terralibext' to enable raii -require "terralibext" +require "terralibext" --load 'terralibext' to enable raii +local test = require "test" -local std = {} -std.io = terralib.includec("stdio.h") +local std = { + io = terralib.includec("stdio.h") +} local function printtestheader(s) print() @@ -40,11 +41,14 @@ A.methods.__copy:adddefinition(terra(from : &A, to : &int) end) +printtestheader("raii.t - testing __init metamethod") terra testinit() var a : A return a.data end +test.eq(testinit(), 1) +printtestheader("raii.t - testing __dtor metamethod") terra testdtor() var x : &int do @@ -53,13 +57,17 @@ terra testdtor() end return @x end +test.eq(testdtor(), -1) +printtestheader("raii.t - testing __copy metamethod in copy-construction") terra testcopyconstruction() var a : A var b = a return b.data end +test.eq(testcopyconstruction(), 11) +printtestheader("raii.t - testing __copy metamethod in copy-assignment") terra testcopyassignment() var a : A a.data = 2 @@ -67,13 +75,17 @@ terra testcopyassignment() b = a return b.data end +test.eq(testcopyassignment(), 12) +printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") terra testcopyassignment1() var a : A a = 3 return a.data end +test.eq(testcopyassignment1(), 3) +printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") terra testcopyassignment2() var a : A var x : int @@ -81,29 +93,4 @@ terra testcopyassignment2() x = a return x end - -local test = require "test" - ---test if __init is called on object initialization to set 'a.data = 1' -printtestheader("raii.t - testing __init metamethod") -test.eq(testinit(), 1) - ---test if __dtor is called at the end of the scope to set 'a.data = -1' -printtestheader("raii.t - testing __dtor metamethod") -test.eq(testdtor(), -1) - ---test if __copy is called in construction 'var b = a' -printtestheader("raii.t - testing __copy metamethod in copy-construction") -test.eq(testcopyconstruction(), 11) - ---test if __copy is called in an assignment 'b = a' -printtestheader("raii.t - testing __copy metamethod in copy-assignment") -test.eq(testcopyassignment(), 12) - ---test if __copy is called in an assignment 'a = 3' -printtestheader("raii.t - testing __copy metamethod in copy-assignment from integer to struct.") -test.eq(testcopyassignment1(), 3) - ---test if __copy is called in an assignment 'x = a' for integer x -printtestheader("raii.t - testing __copy metamethod in copy-assignment from struct to integer.") test.eq(testcopyassignment2(), 5) \ No newline at end of file From 2aeb43316530e09e4d9a0b20b006d8488063eede Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 27 May 2024 23:32:27 +0200 Subject: [PATCH 55/69] fixed throwing error in createassignment in case of a tuple assignment of managed variables with a custom copy method. --- src/terralib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index 523cd0082..e0f6253f5 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3363,7 +3363,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) regular.rhs:insert(rhs[i]) end end - if #byfcall>0 and #byfcall+#regular>1 then + if #byfcall.lhs>0 and #byfcall.lhs+#regular.lhs>1 then --__copy can potentially mutate left and right-handsides in an --assignment. So we prohibit assignments that may involve something --like a swap: u,v = v, u. From 5fcd780aa1cb0668e6ca0520c2fe3e6a4f9f9c06 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 28 May 2024 00:21:26 +0200 Subject: [PATCH 56/69] proper error exception for tuple assignment of managed variables. see test fails/raii-tuple-custom-copy.t --- src/terralib.lua | 2 +- tests/fails/raii-tuple-custom-copy.t | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index e0f6253f5..7eea24583 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3368,7 +3368,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --assignment. So we prohibit assignments that may involve something --like a swap: u,v = v, u. --for now we prohibit this by limiting such assignments - erroratlocation(anchor, "a custom __copy assignment is not supported for tuples.") + diag:reporterror(anchor, "a custom __copy assignment is not supported for tuples.") end return regular, byfcall end diff --git a/tests/fails/raii-tuple-custom-copy.t b/tests/fails/raii-tuple-custom-copy.t index 71a71a08e..5cbabb5a3 100644 --- a/tests/fails/raii-tuple-custom-copy.t +++ b/tests/fails/raii-tuple-custom-copy.t @@ -1,14 +1,9 @@ +if not require("fail") then return end require "terralibext" --load 'terralibext' to enable raii local std = {} std.io = terralib.includec("stdio.h") -local function printtestheader(s) - print() - print("===========================") - print(s) - print("===========================") -end struct A{ data : int @@ -29,7 +24,6 @@ A.methods.__copy = terra(from : &A, to : &A) to.data = from.data+1 end -printtestheader("raii.t - testing custom copy for tuples") terra test0() var a = A{1} var b = A{2} From ee3b76d4d75dfd653e2b8e5ff8ccfdb9667684a0 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 28 May 2024 00:25:23 +0200 Subject: [PATCH 57/69] system code macos in terralib.lua --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7eea24583..481155e05 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3774,11 +3774,11 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert("-isysroot") - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert("-isysroot") + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From dd5efb337e4c781ea7cc36ae9201c820d5ff2fe7 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 13:10:43 +0200 Subject: [PATCH 58/69] lib/raii.md - discussing implementation and use of RAII concepts. --- lib/raii.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 lib/raii.md diff --git a/lib/raii.md b/lib/raii.md new file mode 100644 index 000000000..ca4b27f51 --- /dev/null +++ b/lib/raii.md @@ -0,0 +1,137 @@ +# RAII - Resource management +Resource acquisition is initialization ([RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)) provides a deterministic means of safe resource management. It is generally associated with systems programming languages such as *c++* and *rust*. + +In the following I summarize the experimental implementation that you can find [here](https://github.com/renehiemstra/terra/tree/raii). The socalled [Big Three](https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) are supported: +* object destruction +* copy assignment +* copy construction + +Terra does not support rvalue references (introduced e.g. in *c++11*), so the experimental RAII support is comparable to that of *C++03* or *rust*. + +## Feature summary +Compiler support for the following methods: +``` +A.methods.__init(self : &A) +A.methods.__dtor(self : &A) +(A or B).methods.__copy(from : &A, to : &B) +``` +These methods support the implementation of smart containers and smart pointers, like *std::string*, *std::vector* and *std::unique_ptr*, *std::shared_ptr*, *boost:offset_ptr* in C++. + +The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular C stdlib functions such malloc and free, leaving memory allocation in the hands of the programmer. + +If implemented, these methods are inserted judiciously during the type checking phase, implemented in *terralib.lua*. All these metamethods can be implemented as macro's or as terra functions. + +## Compiler supported methods for RAII +A managed type is one that implements at least `__dtor` and optionally `__init` and `__copy` or, by induction, has fields or subfields that are of a managed type. In the following I assume `struct A` is a managed type. + +### Object initialization +`__init` is used to initialize managed variables: +``` + A.methods.__init(self : &A) +``` +The compiler checks for an `__init` method in any variable definition statement, without explicit initializer, and emits the call right after the variable definition, e.g. +``` + var a : A + a:__init() +``` +### Copy assignment +`__copy` enables specialized copy-assignment and, combined with `__init`, copy construction. `__copy` takes two arguments, which can be different, as long as one of them is a managed type, e.g. +``` + A.metamethods.__copy(from : &A, to : &B) +``` +and / or +``` + A.metamethods.__copy(from : &B, to : &A) +``` +If `a : A` is a managed type, then the compiler will replace a regular assignment by a call to the implemented `__copy` method +``` + b = a ----> A.methods.__copy(a, b) +``` +or +``` + a = b ----> A.methods.__copy(b, a) +``` +`__copy` can be an overloaded terra function or a macro. + +The programmer is responsable for managing any heap resources associated with the arguments of the `__copy` method. + +### Copy construction +In object construction, `__copy` is combined with `__init` to perform copy construction. For example, +``` + var b = a +``` +is replaced by the following statements +``` + var b : B + b:__init() --generated by compiler if an `__init` is implemented + A.methods.__copy(a, b) +``` +If the right `__copy` method is not implemented but a user defined `__cast` metamethod exists that can cast one of the arguments to the correct type, then the cast is performed and then the relevant copy method is applied. + +### Object destruction +`__dtor` can be used to free heap memory +``` + A.methods.__dtor(self : &A) +``` +The implementation adds a deferred call to `__dtor ` near the end of a scope, right before a potential return statement, for all variables local to the current scope that are not returned. Hence, `__dtor` is tied to the lifetime of the object. For example, for a block of code the compiler would generate +``` +do + var x : A, y : A + ... + ... + defer x:__dtor() --generated by compiler + defer y:__dtor() --generated by compiler +end +``` +or in case of a terra function +``` +terra foo(x : A) + var y : A, z : A + ... + ... + defer z:__dtor() --generated by compiler + return y +end +``` +`__dtor` is also called before any regular assignment (if a __copy method is not implemented) to free 'old' resources. So +``` + a = b +``` +is replaced by +``` + a:__dtor() --generated by compiler + a = b +``` +## Compositional API's +If a struct has fields or subfields that are managed types, but do not implement `__init`, `__copy` or `__dtor`, then the compiler will generate default methods that inductively call existing `__init`, `__copy` or `__dtor` methods for its fields and subfields. This enables compositional API's like `vector(vector(int))` or `vector(string)`. This is implemented as an extension to *terralib.lua* in *lib/terralibext.t*. + +## Current limitations +* Tuple (copy) assignement (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as +``` + a, b = b, a +``` +* Currently, there is no way to prevent unwanted calls to `__dtor` in cases such as the following. Consider +``` +terra foo() + var b : A + return bar(b) +end +``` +which will get expanded to +``` +terra foo() + var b : A + defer b:__dtor() --generated by compiler + return bar(b) +end +``` +If `bar` would return `b` then its associated heap resources would be released before they can be used in the outer scope. + +## Roadmap +The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is: +* support for *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. +* borrow checking (similar to *rust*) by counting, at compile time, the number of references. + + +## Examples +The check for metamethods.__dtor is done once in checkblock(...) (which checks a scoped environment) and metamethods.(__init, __copy, __dtor) are checked in several parts of checkassignment(...). These checks are cheap, especially if none of the metamethods are implemented. \ No newline at end of file From 50abd4abd6e8451d01ae7fac8fb4eec93df59520 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 13:40:03 +0200 Subject: [PATCH 59/69] addmissing generates a missing method only once. --- lib/raii.md | 2 +- lib/terralibext.t | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/raii.md b/lib/raii.md index ca4b27f51..472b7311a 100644 --- a/lib/raii.md +++ b/lib/raii.md @@ -106,7 +106,7 @@ is replaced by If a struct has fields or subfields that are managed types, but do not implement `__init`, `__copy` or `__dtor`, then the compiler will generate default methods that inductively call existing `__init`, `__copy` or `__dtor` methods for its fields and subfields. This enables compositional API's like `vector(vector(int))` or `vector(string)`. This is implemented as an extension to *terralib.lua* in *lib/terralibext.t*. ## Current limitations -* Tuple (copy) assignement (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as +* Tuple (copy) assignment (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as ``` a, b = b, a ``` diff --git a/lib/terralibext.t b/lib/terralibext.t index 0ff0d754e..4d44af8fd 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -57,13 +57,17 @@ local function addmissinginit(T) return stmts end) - if T:isstruct() and not T.methods.__init then + if T:isstruct() and T.methods.__init == nil then local method = terra(self : &T) std.io.printf("%s:__init - generated\n", [tostring(T)]) generateinit(@self) end if generate then T.methods.__init = method + else + --set T.methods.__init to false. This means that addmissinginit(T) will not + --attempt to generate 'T.methods.__init' twice + T.methods.__init = false end end end @@ -120,13 +124,17 @@ local function addmissingdtor(T) return stmts end) - if T:isstruct() and not T.methods.__dtor then + if T:isstruct() and T.methods.__dtor==nil then local method = terra(self : &T) std.io.printf("%s:__dtor - generated\n", [tostring(T)]) generatedtor(@self) end if generate then T.methods.__dtor = method + else + --set T.methods.__dtor to false. This means that addmissingdtor(T) will not + --attempt to generate 'T.methods.__dtor' twice + T.methods.__dtor = false end end end @@ -191,13 +199,17 @@ local function addmissingcopy(T) return stmts end) - if T:isstruct() and not T.methods.__copy then + if T:isstruct() and T.methods.__copy==nil then local method = terra(from : &T, to : &T) std.io.printf("%s:__copy - generate\n", [tostring(T)]) generatecopy(@from, @to) end if generate then T.methods.__copy = method + else + --set T.methods.__copy to false. This means that addmissingcopy(T) will not + --attempt to generate 'T.methods.__copy' twice + T.methods.__copy = false end end end From ae586788c581e607f5e5c565c7c145a06ff45ca7 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 15:21:20 +0200 Subject: [PATCH 60/69] prohibiting assignments of managed objects consisting of more that 1 assignment. --- src/terralib.lua | 39 +++++++++++++------------ tests/fails/raii-tuple-default-copy.t | 38 ++++++++++++++++++++++++ tests/raii-tuple-default-copy.t | 42 --------------------------- 3 files changed, 59 insertions(+), 60 deletions(-) create mode 100644 tests/fails/raii-tuple-default-copy.t delete mode 100644 tests/raii-tuple-default-copy.t diff --git a/src/terralib.lua b/src/terralib.lua index 481155e05..7c1b9dd2b 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3368,7 +3368,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --assignment. So we prohibit assignments that may involve something --like a swap: u,v = v, u. --for now we prohibit this by limiting such assignments - diag:reporterror(anchor, "a custom __copy assignment is not supported for tuples.") + diag:reporterror(anchor, "assignments of managed objects is not supported for tuples.") end return regular, byfcall end @@ -3429,18 +3429,22 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else ensurelvalue(v) --if 'v' is a managed variable then - --(1) var tmp_v : v.type = rhs[i] --evaluate the rhs and store in tmp_v - --(2) v:__dtor() --delete old v - --(3) v = tmp_v --copy new data to v - --these steps are needed because rhs[i] may be a function of v, and the assignment in 'anchor' - --could involve something like a swap: u, v = v, u + --(1) var tmp = v --store v in tmp + --(2) v = rhs[i] --perform assignment + --(3) tmp:__dtor() --delete old v + --the temporary is necessary because rhs[i] may involve a function of 'v' if hasmetamethod(v, "__dtor") then - local tmpa_v, tmp_v = allocvar(v, v.type,"") - --define temporary variable as new left-hand-side - regular.lhs[i] = tmpa_v - --call v:__dtor() and set v = tmp_v - post:insert(checkmetadtor(anchor, v)) - post:insert(newobject(anchor,T.assignment, List{v}, List{tmp_v})) + --To avoid unwanted deletions we prohibit assignments that may involve something + --like a swap: u,v = v, u. + --for now we prohibit this by limiting assignments to a single one + if #regular.lhs>1 then + diag:reporterror(anchor, "assignments of managed objects is not supported for tuples.") + end + local tmpa, tmp = allocvar(v, v.type,"") + --store v in tmp + stmts:insert(newobject(anchor,T.assignment, List{tmpa}, List{v})) + --call tmp:__dtor() + post:insert(checkmetadtor(anchor, tmp)) end end end @@ -3468,7 +3472,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) end end - if #stmts==0 and #post==0 then + if #stmts==0 then --standard case, no meta-copy-assignments return newobject(anchor,T.assignment, regular.lhs, regular.rhs) else @@ -3774,11 +3778,10 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - local sdkroot = os.getenv("SDKROOT") - if sdkroot then - args:insert("-isysroot") - args:insert(sdkroot) - end + --local sdkroot = os.getenv("SDKROOT") + --if sdkroot then + -- args:insert(sdkroot) + --end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") diff --git a/tests/fails/raii-tuple-default-copy.t b/tests/fails/raii-tuple-default-copy.t new file mode 100644 index 000000000..fa896ad01 --- /dev/null +++ b/tests/fails/raii-tuple-default-copy.t @@ -0,0 +1,38 @@ +if not require("fail") then return end +require "terralibext" --load 'terralibext' to enable raii +local test = require "test" + +local std = {} +std.io = terralib.includec("stdio.h") + +local function printtestheader(s) + print() + print("===========================") + print(s) + print("===========================") +end + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + std.io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + std.io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +terra test0() + var a = A{1} + var b = A{2} + a, b = b, a --bitcopies don't work in a swap + --tuple assignments are prohibited because proper + --resource management cannot be guaranteed + --(at least not yet) + return a.data, b.data +end +test0() \ No newline at end of file diff --git a/tests/raii-tuple-default-copy.t b/tests/raii-tuple-default-copy.t deleted file mode 100644 index 83a6eee6f..000000000 --- a/tests/raii-tuple-default-copy.t +++ /dev/null @@ -1,42 +0,0 @@ -require "terralibext" --load 'terralibext' to enable raii -local test = require "test" - -local std = {} -std.io = terralib.includec("stdio.h") - -local function printtestheader(s) - print() - print("===========================") - print(s) - print("===========================") -end - -struct A{ - data : int -} - -A.methods.__init = terra(self : &A) - std.io.printf("__init: calling initializer.\n") - self.data = 1 -end - -A.methods.__dtor = terra(self : &A) - std.io.printf("__dtor: calling destructor.\n") - self.data = -1 -end - -printtestheader("raii.t - testing default copy metamethod") -terra test0() - var a = A{1} - var b = A{2} - a, b = b, a --bitcopies should work in a swap - --the following code is generated - --var tmp_a = __move(b) --store evaluated rhs in a tmp variable - --var tmp_b = __move(a) --store evaluated rhs in a tmp variable - --a:__dtor() --delete old memory (nothing happens as 'a' has been moved from) - --b:__dtor() --delete old memory (nothing happens as 'a' has been moved from) - --a = __move(tmp_a) --move new data into 'a' - --b = __move(tmp_b) --move new data into 'b' - return a.data, b.data -end -test.meq({2, 1}, test0()) \ No newline at end of file From 5c31ea59e1dfe6d8d3ac89ee95079946872b14f1 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 19:59:08 +0200 Subject: [PATCH 61/69] forgot to uncomment system code for macos. --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7c1b9dd2b..43028f5fb 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3395,7 +3395,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end - if #lhs < #rhs then + if not terralib.ext or #lhs < #rhs then --an error may be reported later during type-checking: 'expected #lhs parameters (...), but found #rhs (...)' local vtypes = lhs:map(function(v) return v.type or "passthrough" end) rhs = insertcasts(anchor, vtypes, rhs) @@ -3778,10 +3778,10 @@ function terra.includecstring(code,cargs,target) args:insert(path) end -- Obey the SDKROOT variable on macOS to match Clang behavior. - --local sdkroot = os.getenv("SDKROOT") - --if sdkroot then - -- args:insert(sdkroot) - --end + local sdkroot = os.getenv("SDKROOT") + if sdkroot then + args:insert(sdkroot) + end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 if ffi.os ~= "Windows" and terralib.llvm_version >= 100 then args:insert("-fgnuc-version=4.2.1") From ec311adb2ecf72621b5a93693dc6ffbbb0b0a861 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 20:00:41 +0200 Subject: [PATCH 62/69] updated /lib/raii.md --- lib/raii.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/raii.md b/lib/raii.md index 472b7311a..5f2d5e529 100644 --- a/lib/raii.md +++ b/lib/raii.md @@ -130,8 +130,4 @@ If `bar` would return `b` then its associated heap resources would be released b ## Roadmap The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is: * support for *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. -* borrow checking (similar to *rust*) by counting, at compile time, the number of references. - - -## Examples -The check for metamethods.__dtor is done once in checkblock(...) (which checks a scoped environment) and metamethods.(__init, __copy, __dtor) are checked in several parts of checkassignment(...). These checks are cheap, especially if none of the metamethods are implemented. \ No newline at end of file +* borrow checking (similar to *rust*) by counting, at compile time, the number of references. \ No newline at end of file From 35040c6067cb2e868494a36e1be583000873fe4e Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 29 May 2024 20:31:17 +0200 Subject: [PATCH 63/69] updated /lib/raii.md --- lib/raii.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/raii.md b/lib/raii.md index 5f2d5e529..8554295d3 100644 --- a/lib/raii.md +++ b/lib/raii.md @@ -126,8 +126,10 @@ terra foo() end ``` If `bar` would return `b` then its associated heap resources would be released before they can be used in the outer scope. +* The implementation is not aware of when an actual heap allocation is made and therefore assumes that a managed variable always carries a heap resource. It is up to the programmer to properly initialize pointer variables to nil to avoid calling 'free' on uninitialized pointers. ## Roadmap -The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is: -* support for *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. -* borrow checking (similar to *rust*) by counting, at compile time, the number of references. \ No newline at end of file +The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is to: +* support *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. +* support borrow checking (similar to *rust*) by counting, at compile time, the number of references. +* introduce a `__new` method that does heap allocation for the programmer. This way the compiler is made aware of all heap allocations being made. \ No newline at end of file From 12f61280c8192c41e8554d79eeedde1716bc47bf Mon Sep 17 00:00:00 2001 From: renehiemstra <152627545+renehiemstra@users.noreply.github.com> Date: Fri, 31 May 2024 20:04:30 +0200 Subject: [PATCH 64/69] Update terralib.lua undid accidental deletion of macos systemcode. --- src/terralib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/terralib.lua b/src/terralib.lua index 43028f5fb..412657967 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3780,6 +3780,7 @@ function terra.includecstring(code,cargs,target) -- Obey the SDKROOT variable on macOS to match Clang behavior. local sdkroot = os.getenv("SDKROOT") if sdkroot then + args:insert("-isysroot") args:insert(sdkroot) end -- Set GNU C version to match value set by Clang: https://github.com/llvm/llvm-project/blob/f77c948d56b09b839262e258af5c6ad701e5b168/clang/lib/Driver/ToolChains/Clang.cpp#L5750-L5753 From 296ba5ca021839186937a0cdc890bffc3923b2b3 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Wed, 5 Jun 2024 11:05:13 +0200 Subject: [PATCH 65/69] updated /lib/raii.md --- lib/raii.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/raii.md b/lib/raii.md index 8554295d3..5a7771511 100644 --- a/lib/raii.md +++ b/lib/raii.md @@ -17,13 +17,19 @@ A.methods.__dtor(self : &A) ``` These methods support the implementation of smart containers and smart pointers, like *std::string*, *std::vector* and *std::unique_ptr*, *std::shared_ptr*, *boost:offset_ptr* in C++. -The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular C stdlib functions such malloc and free, leaving memory allocation in the hands of the programmer. +The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular *C stdlib* functions such as malloc and free, leaving memory allocation in the hands of the programmer. If implemented, these methods are inserted judiciously during the type checking phase, implemented in *terralib.lua*. All these metamethods can be implemented as macro's or as terra functions. ## Compiler supported methods for RAII A managed type is one that implements at least `__dtor` and optionally `__init` and `__copy` or, by induction, has fields or subfields that are of a managed type. In the following I assume `struct A` is a managed type. +To enable RAII, import the library */lib/terralibext.t* using +``` + require "terralibext" +``` +The compiler only checks for `__init`, `__dtor` and `__copy` in case this libreary is loaded. + ### Object initialization `__init` is used to initialize managed variables: ``` @@ -32,7 +38,7 @@ A managed type is one that implements at least `__dtor` and optionally `__init` The compiler checks for an `__init` method in any variable definition statement, without explicit initializer, and emits the call right after the variable definition, e.g. ``` var a : A - a:__init() + a:__init() --generated by compiler ``` ### Copy assignment `__copy` enables specialized copy-assignment and, combined with `__init`, copy construction. `__copy` takes two arguments, which can be different, as long as one of them is a managed type, e.g. @@ -51,20 +57,20 @@ or ``` a = b ----> A.methods.__copy(b, a) ``` -`__copy` can be an overloaded terra function or a macro. +`__copy` can be a (overloaded) terra function or a macro. The programmer is responsable for managing any heap resources associated with the arguments of the `__copy` method. ### Copy construction In object construction, `__copy` is combined with `__init` to perform copy construction. For example, ``` - var b = a + var b : B = a ``` is replaced by the following statements ``` var b : B - b:__init() --generated by compiler if an `__init` is implemented - A.methods.__copy(a, b) + b:__init() --generated by compiler if `__init` is implemented + A.methods.__copy(a, b) --generated by compiler ``` If the right `__copy` method is not implemented but a user defined `__cast` metamethod exists that can cast one of the arguments to the correct type, then the cast is performed and then the relevant copy method is applied. @@ -105,7 +111,19 @@ is replaced by ## Compositional API's If a struct has fields or subfields that are managed types, but do not implement `__init`, `__copy` or `__dtor`, then the compiler will generate default methods that inductively call existing `__init`, `__copy` or `__dtor` methods for its fields and subfields. This enables compositional API's like `vector(vector(int))` or `vector(string)`. This is implemented as an extension to *terralib.lua* in *lib/terralibext.t*. +## Examples +The following files have been added to the terra testsuite: +* *raii.t* tests whether `__dtor`, `__init`, and `__copy` are evaluated correctly for simple datatypes. +* *raii-copyctr-cast.t* tests the combination of `metamethods.__cast` and `__copy`. +* *raii-unique_ptr.t* tests some functionality of a unique pointer type. +* *raii-shared_ptr.t* tests some functionality of a shared pointer type. +* *raii-offset_ptr.t* tests some functionality of an offset pointer implementation, found e.g. in *boost.cpp*. +* *raii-compose.t* tests the compositional aspect. + +You can have a look there for some common code patterns. + ## Current limitations +* The implementation is not aware of when an actual heap allocation is made and therefore assumes that a managed variable always carries a heap resource. It is up to the programmer to properly initialize pointer variables to nil to avoid calling 'free' on uninitialized pointers. * Tuple (copy) assignment (regular or using `__copy`) are prohibited by the compiler in case of managed variables. This is done to prevent memory leaks or unwanted deletions in assignments such as ``` a, b = b, a @@ -126,10 +144,9 @@ terra foo() end ``` If `bar` would return `b` then its associated heap resources would be released before they can be used in the outer scope. -* The implementation is not aware of when an actual heap allocation is made and therefore assumes that a managed variable always carries a heap resource. It is up to the programmer to properly initialize pointer variables to nil to avoid calling 'free' on uninitialized pointers. ## Roadmap The current implementation already works in a range of important applications, as can be seen in the tests and the examples above. To remove the noted limitations and to enable graceful compile-time errors my plan is to: * support *affine types* (similar to *rust*) by checking, at compile time, that a managed variable is used (passed by value) not more than once. This essentially means that the variable is moved from (not copied) on every use. This is not restrictive, since in general you would pass managed objects by reference, not by value. * support borrow checking (similar to *rust*) by counting, at compile time, the number of references. -* introduce a `__new` method that does heap allocation for the programmer. This way the compiler is made aware of all heap allocations being made. \ No newline at end of file +* introduce a `__new` method that signals a heap allocation. This way the compiler is made aware of all heap allocations being made. \ No newline at end of file From e020696ee52b73059adc539bbae8acc38bbeff1d Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 27 Jun 2024 20:45:14 +0200 Subject: [PATCH 66/69] added terralibext.t to list of terra files that are to be installed. --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1a8e114ea..61e827d8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ add_custom_command( DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/geninternalizedfiles.lua" "${PROJECT_SOURCE_DIR}/lib/std.t" + "${PROJECT_SOURCE_DIR}/lib/terralibext.t" "${PROJECT_SOURCE_DIR}/lib/parsing.t" LuaJIT COMMAND ${LUAJIT_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/geninternalizedfiles.lua" ${PROJECT_BINARY_DIR}/internalizedfiles.h ${CLANG_RESOURCE_DIR} "%.h$" ${CLANG_RESOURCE_DIR} "%.modulemap$" "${PROJECT_SOURCE_DIR}/lib" "%.t$" From eaff0904f1b470274c9bd860d803f90c66c7007c Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 27 Aug 2024 20:06:03 +0200 Subject: [PATCH 67/69] fixed issue with assignment of managed variables where a temporary allocvar is created. --- src/terralib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terralib.lua b/src/terralib.lua index e8229ca75..7f1f48628 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3445,7 +3445,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if #regular.lhs>1 then diag:reporterror(anchor, "assignments of managed objects is not supported for tuples.") end - local tmpa, tmp = allocvar(v, v.type,"") + local tmpa, tmp = allocvar(v, v.type,"") --store v in tmp stmts:insert(newobject(anchor,T.assignment, List{tmpa}, List{v})) --call tmp:__dtor() From 487cf69798ed7ebfff8f604c754fb1b91fc0e645 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Tue, 27 Aug 2024 20:07:17 +0200 Subject: [PATCH 68/69] removed the print statements in terralibext.t in the generated __dtor's. --- lib/terralibext.t | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index 4d44af8fd..1bf8e1636 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -1,7 +1,3 @@ -local std = { - io = terralib.includec("stdio.h") -} - local function addmissinginit(T) --flag that signals that a missing __init method needs to @@ -59,7 +55,6 @@ local function addmissinginit(T) if T:isstruct() and T.methods.__init == nil then local method = terra(self : &T) - std.io.printf("%s:__init - generated\n", [tostring(T)]) generateinit(@self) end if generate then @@ -126,7 +121,6 @@ local function addmissingdtor(T) if T:isstruct() and T.methods.__dtor==nil then local method = terra(self : &T) - std.io.printf("%s:__dtor - generated\n", [tostring(T)]) generatedtor(@self) end if generate then @@ -201,7 +195,6 @@ local function addmissingcopy(T) if T:isstruct() and T.methods.__copy==nil then local method = terra(from : &T, to : &T) - std.io.printf("%s:__copy - generate\n", [tostring(T)]) generatecopy(@from, @to) end if generate then From 477ba23480299317fa7b3e09e91ca4bb42a2ea8e Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Sat, 7 Sep 2024 16:03:36 +0200 Subject: [PATCH 69/69] fixed pasted-copy bug in __addmissingcopy - where hasdtor should have been hascopy. --- lib/terralibext.t | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index 1bf8e1636..8777e0a0d 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -1,3 +1,5 @@ +--local io = terralib.includec("stdio.h") + local function addmissinginit(T) --flag that signals that a missing __init method needs to @@ -56,6 +58,7 @@ local function addmissinginit(T) if T:isstruct() and T.methods.__init == nil then local method = terra(self : &T) generateinit(@self) + --io.printf("generated __init()\n") end if generate then T.methods.__init = method @@ -121,6 +124,7 @@ local function addmissingdtor(T) if T:isstruct() and T.methods.__dtor==nil then local method = terra(self : &T) + --io.printf("generated __dtor()\n") generatedtor(@self) end if generate then @@ -163,11 +167,11 @@ local function addmissingcopy(T) to = from end end - elseif V:isarray() and hasdtor(V) then + elseif V:isarray() and hascopy(V) then return quote var pa = &self for i = 0,V.N do - rundtor((@pa)[i]) + runcopy((@pa)[i]) end end else @@ -196,6 +200,7 @@ local function addmissingcopy(T) if T:isstruct() and T.methods.__copy==nil then local method = terra(from : &T, to : &T) generatecopy(@from, @to) + --io.printf("generated __copy()\n") end if generate then T.methods.__copy = method