From e248051d3ce6c84dd5eb184a845e0f2ec97ae65f Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 17 May 2024 22:24:26 +0200 Subject: [PATCH 01/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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/81] 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 From 86a38731ba5ea472a0816ea0f863f89c966769d3 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 30 Sep 2024 22:11:21 +0200 Subject: [PATCH 70/81] added __move, __forward to terralibext.t --- lib/terralibext.t | 151 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 33 deletions(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index 8777e0a0d..c35375905 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -1,5 +1,22 @@ ---local io = terralib.includec("stdio.h") +--__forward takes a value by reference and simply forwards it by reference, +--creating an rvalue +local function addmissingforward(T) + if T:isstruct() then + if T.methods.__forward then + T.methods.__forward_generated = T.methods.__forward + return + end + if not T.methods.__forward and not T.methods.__forward_generated then + T.methods.__forward_generated = terra(self : &T) + return self --simply forward the variable (turning it into an rvalue) + end + T.methods.__forward = T.methods.__forward_generated + return + end + end +end +--__create a missing __init for 'T' and all its entries local function addmissinginit(T) --flag that signals that a missing __init method needs to @@ -55,22 +72,30 @@ local function addmissinginit(T) return stmts end) - if T:isstruct() and T.methods.__init == nil then - local method = terra(self : &T) - generateinit(@self) - --io.printf("generated __init()\n") + if T:isstruct() then + --__init is implemented + if T.methods.__init and not T.methods.__init_generated then + T.methods.__init_generated = T.methods.__init + return 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 + --__dtor is not implemented + if not T.methods.__init and not T.methods.__init_generated then + T.methods.__init_generated = terra(self : &T) + generateinit(@self) + end + if generate then + T.methods.__init = T.methods.__init_generated + 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 + return end end end - +--__create a missing __dtor for 'T' and all its entries local function addmissingdtor(T) --flag that signals that a missing __dtor method needs to @@ -122,21 +147,31 @@ local function addmissingdtor(T) return stmts end) - if T:isstruct() and T.methods.__dtor==nil then - local method = terra(self : &T) - --io.printf("generated __dtor()\n") - generatedtor(@self) + if T:isstruct() then + --__dtor is implemented + if T.methods.__dtor and not T.methods.__dtor_generated then + T.methods.__dtor_generated = T.methods.__dtor + return 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 + --__dtor is not implemented + if not T.methods.__dtor and not T.methods.__dtor_generated then + --generate __dtor + T.methods.__dtor_generated = terra(self : &T) + generatedtor(@self) + end + if generate then + T.methods.__dtor = T.methods.__dtor_generated + 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 + return end end end +--__create a missing __copy for 'T' and all its entries local function addmissingcopy(T) --flag that signals that a missing __copy method needs to @@ -197,25 +232,75 @@ local function addmissingcopy(T) return stmts end) - if T:isstruct() and T.methods.__copy==nil then - local method = terra(from : &T, to : &T) - generatecopy(@from, @to) - --io.printf("generated __copy()\n") + if T:isstruct() then + --__copy is implemented + if T.methods.__copy and not T.methods.__copy_generated then + T.methods.__copy_generated = T.methods.__copy + return 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 + --__copy is not implemented + if not T.methods.__copy and not T.methods.__copy_generated then + --generate __copy + T.methods.__copy_generated = terra(from : &T, to : &T) + generatecopy(@from, @to) + end + if generate then + T.methods.__copy = T.methods.__copy_generated + 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 + return + end + end +end + +--generate __move, which moves resources to a new allocated variable +local function addmissingmove(T) + if T:isstruct() then + if T.methods.__move and not T.methods.__move_generated then + T.methods.__move_generated = T.methods.__move + return + end + + if not T.methods.__move and not T.methods.__move_generated then + --generate missing __forward and __init + addmissingforward(T) + addmissinginit(T) + --if an __init was generated then we can generate a specialized __move + if T.methods.__init then + T.methods.__move_generated = terra(self : &T) + var new = self:__forward_generated() --shallow copy of 'self' + self:__init_generated() --initialize old 'self' + return new + end + T.methods.__move = T.methods.__move_generated + --otherwise, __move is just __forward and is accessible only in __move_generated + else + T.methods.__move_generated = T.methods.__forward_generated + T.methods.__move = false + end + return end end end +local function addmissingraii(T) + addmissingforward(T) + addmissingdinit(T) + addmissingdtor(T) + addmissingcopy(T) + addmissingmove(T) +end + terralib.ext = { addmissing = { + __forward = addmissingforward, __init = addmissinginit, __dtor = addmissingdtor, - __copy = addmissingcopy + __copy = addmissingcopy, + __move = addmissingmove, + __all = addmissingraii } } \ No newline at end of file From 829ff63fef951f47739f830ce60d8ae377254252 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Thu, 17 Oct 2024 11:54:26 +0200 Subject: [PATCH 71/81] passing by value in __generate_move in lib/terralibext.t --- lib/terralibext.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/terralibext.t b/lib/terralibext.t index c35375905..2ee8e3f90 100644 --- a/lib/terralibext.t +++ b/lib/terralibext.t @@ -271,7 +271,7 @@ local function addmissingmove(T) --if an __init was generated then we can generate a specialized __move if T.methods.__init then T.methods.__move_generated = terra(self : &T) - var new = self:__forward_generated() --shallow copy of 'self' + var new = @self:__forward_generated() --shallow copy of 'self' self:__init_generated() --initialize old 'self' return new end From 099ae98e194638ec9da59284b5f61fabc2ba9e31 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 22 Nov 2024 16:43:26 +0100 Subject: [PATCH 72/81] direct initialization with managed members now works as expected, calling the copyconstructor of the menaged members. --- src/terralib.lua | 761 ++++++++++++++++++++++++----------------------- 1 file changed, 383 insertions(+), 378 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index 7f1f48628..ddab47436 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -47,7 +47,7 @@ ident = escapedident(luaexpression expression) # removed during specializati field = recfield(ident key, tree value) | listfield(tree value) - + structbody = structentry(string key, luaexpression type) | structlist(structbody* entries) @@ -62,20 +62,20 @@ cmpxchgattr = (string? syncscope, string success_ordering, string failure_orderi atomicattr = (string? syncscope, string ordering, number? alignment, boolean isvolatile) Symbol = (Type type, string displayname, number id) Label = (string displayname, number id) -tree = +tree = # trees that are introduced in parsing and are ... # removed during specialization luaexpression(function expression, boolean isexpression) # removed during typechecking | constructoru(field* records) #untyped version | selectu(tree value, ident field) #untyped version - | method(tree value,ident name,tree* arguments) + | method(tree value,ident name,tree* arguments) | statlist(tree* statements) | fornumu(param variable, tree initial, tree limit, tree? step,block body) #untyped version | defvar(param* variables, boolean hasinit, tree* initializers) | forlist(param* variables, tree iterator, block body) | functiondefu(param* parameters, boolean is_varargs, TypeOrLuaExpression? returntype, block body) - + # introduced temporarily during specialization/typing, but removed after typing | luaobject(any value) | setteru(function setter) # temporary node introduced and removed during typechecking to handle __update and __setfield @@ -117,7 +117,7 @@ tree = | constructor(tree* expressions) | returnstat(tree expression) | setter(allocvar rhs, tree setter) # handles custom assignment behavior, real rhs is first stored in 'rhs' and then the 'setter' expression uses it - + # special purpose nodes, they only occur in specific locations, but are considered trees because they can contain typed trees | ifbranch(tree condition, block body) | switchcase(tree condition, block body) @@ -139,11 +139,11 @@ labelstate = undefinedlabel(gotostat * gotos, table* positions) #undefined label definition = functiondef(string? name, functype type, allocvar* parameters, boolean is_varargs, block body, table labeldepths, globalvalue* globalsused) | functionextern(string? name, functype type) - + globalvalue = terrafunction(definition? definition) | globalvariable(tree? initializer, number addressspace, boolean extern, boolean constant) attributes(string name, Type type, table anchor) - + overloadedterrafunction = (string name, terrafunction* definitions) ]] terra.irtypes = T @@ -160,7 +160,7 @@ local tokens = setmetatable({},{__index = function(self,idx) return idx end }) terra.isverbose = 0 --set by C api -local function dbprint(level,...) +local function dbprint(level,...) if terra.isverbose >= level then print(...) end @@ -182,7 +182,7 @@ end function T.tree:is(value) return self.kind == value end - + function terra.printraw(self) local function header(t) local mt = getmetatable(t) @@ -275,7 +275,7 @@ function terra.newanchor(depth) return setmetatable(body,terra.tree) end -function terra.istree(v) +function terra.istree(v) return T.tree:isclassof(v) end @@ -316,7 +316,7 @@ function terra.newenvironment(_luaenv) __index = function(_,idx) return self._localenv[idx] or self._luaenv[idx] end; - __newindex = function() + __newindex = function() error("cannot define global variables or assign to upvalues in an escape") end; }) @@ -341,12 +341,12 @@ local function formaterror(anchor,...) errlist:insert(anchor.filename..":"..anchor.linenumber..": ") for i = 1,select("#",...) do errlist:insert(tostring(select(i,...))) end errlist:insert("\n") - if not anchor.offset then + if not anchor.offset then return errlist:concat() end - + local filename = anchor.filename - local filetext = diagcache[filename] + local filetext = diagcache[filename] if not filetext then local file = io.open(filename,"r") if file then @@ -367,7 +367,7 @@ local function formaterror(anchor,...) while finish < filetext:len() and filetext:byte(finish + 1) ~= NL do finish = finish + 1 end - local line = filetext:sub(begin,finish) + local line = filetext:sub(begin,finish) errlist:insert(line) errlist:insert("\n") for i = begin,anchor.offset do @@ -458,7 +458,7 @@ function debug.traceback(msg,level) local whatname,what = debug.getlocal(level,2) assert(anchorname == "anchor" and whatname == "what") lines:insert("\n\t") - lines:insert(formaterror(anchor,"Errors reported during "..what):sub(1,-2)) + lines:insert(formaterror(anchor,"Errors reported during "..what):sub(1,-2)) else local short_src,currentline,linedefined = di.short_src,di.currentline,di.linedefined local file,outsideline = di.source:match("^@$terra$(.*)$terra$(%d+)$") @@ -477,7 +477,7 @@ function debug.traceback(msg,level) elseif di.what == "main" then lines:insert(" in main chunk") elseif di.what == "C" then - lines:insert( (" at %s"):format(tostring(di.func))) + lines:insert( (" at %s"):format(tostring(di.func))) else lines:insert((" in function <%s:%d>"):format(short_src,linedefined)) end @@ -555,13 +555,13 @@ function T.terrafunction:setinlined(v) assert(self:isdefined(), "attempting to set the inlining state of an undefined function") self.definition.alwaysinline = not not v assert(not (self.definition.alwaysinline and self.definition.dontoptimize), - "setinlined(true) and setoptimized(false) are incompatible") + "setinlined(true) and setoptimized(false) are incompatible") end function T.terrafunction:setoptimized(v) assert(self:isdefined(), "attempting to set the optimization state of an undefined function") self.definition.dontoptimize = not v assert(not (self.definition.alwaysinline and self.definition.dontoptimize), - "setinlined(true) and setoptimized(false) are incompatible") + "setinlined(true) and setoptimized(false) are incompatible") end function T.terrafunction:setcallingconv(v) assert(self:isdefined(), "attempting to set the calling convention of an undefined function") @@ -584,8 +584,8 @@ function T.terrafunction:printstats() end function T.terrafunction:isextern() return self.definition and self.definition.kind == "functionextern" end function T.terrafunction:isdefined() return self.definition ~= nil end -function T.terrafunction:setname(name) - self.name = tostring(name) +function T.terrafunction:setname(name) + self.name = tostring(name) if self.definition then self.definition.name = name end return self end @@ -595,19 +595,19 @@ function T.terrafunction:adddefinition(functiondef) self:resetdefinition(functiondef) end function T.terrafunction:resetdefinition(functiondef) - if T.terrafunction:isclassof(functiondef) and functiondef:isdefined() then + if T.terrafunction:isclassof(functiondef) and functiondef:isdefined() then functiondef = functiondef.definition end assert(T.definition:isclassof(functiondef), "expected a defined terra function") if self.readytocompile then error("cannot reset a definition of function that has already been compiled",2) end - if self.type ~= functiondef.type and self.type ~= terra.types.placeholderfunction then + if self.type ~= functiondef.type and self.type ~= terra.types.placeholderfunction then error(("attempting to define terra function declaration with type %s with a terra function definition of type %s"):format(tostring(self.type),tostring(functiondef.type))) end self.definition,self.type,functiondef.name = functiondef,functiondef.type,assert(self.name) end function T.terrafunction:gettype(nop) assert(nop == nil, ":gettype no longer takes any callbacks for when a function is complete") - if self.type == terra.types.placeholderfunction then + if self.type == terra.types.placeholderfunction then error("function being recursively referenced needs an explicit return type, function defintion at: "..formaterror(self.anchor,""),2) end return self.type @@ -673,7 +673,7 @@ local function constantcheck(e,checklvalue) if e.expression.type:isarray() then if checklvalue then constantcheck(e.expression,true) - else + else erroratlocation(e,"non-constant cast of array to pointer used as a constant initializer") end else constantcheck(e.expression) end @@ -684,7 +684,7 @@ local function constantcheck(e,checklvalue) else erroratlocation(e,"non-constant expression being used as a constant initializer") end - return e + return e end local function createglobalinitializer(anchor, typ, c) @@ -760,7 +760,7 @@ function terra.newtarget(tbl) return setmetatable({ llvm_target = cdatawithdestructor(terra.inittarget(Triple,CPU,Features,FloatABIHard),terra.freetarget), Triple = Triple, cnametostruct = { general = {}, tagged = {}} --map from llvm_name -> terra type used to make c structs unique per llvm_name - },terra.target) + },terra.target) end function terra.target:getorcreatecstruct(displayname,tagged) local namespace @@ -797,7 +797,7 @@ local function createoptimizationprofile(profile) end -- Ok, leave it alone. else - error("expected fastmath to be a boolean or string but found " .. type(fastmath)) + error("expected fastmath to be a boolean or string but found " .. type(fastmath)) end profile["fastmath"] = fastmath -- Write it back. @@ -810,7 +810,7 @@ compilationunit.__index = compilationunit function terra.newcompilationunit(target,opt,profile) assert(terra.istarget(target),"expected a target object") profile = createoptimizationprofile(profile) - return setmetatable({ symbols = newweakkeytable(), + return setmetatable({ symbols = newweakkeytable(), collectfunctions = opt, llvm_cu = cdatawithdestructor(terra.initcompilationunit(target.llvm_target,opt,profile),terra.freecompilationunit) },compilationunit) -- mapping from Types,Functions,Globals,Constants -> llvm value associated with them for this compilation end @@ -863,7 +863,7 @@ end function terra.createmacro(fromterra,fromlua) return setmetatable({fromterra = fromterra,fromlua = fromlua}, terra.macro) end -function terra.internalmacro(...) +function terra.internalmacro(...) local m = terra.createmacro(...) m._internal = true return m @@ -915,7 +915,7 @@ function T.quote:asvalue() elseif e:is "constructor" then local t,typ = {},e.type for i,r in ipairs(typ:getentries()) do - local v,e = getvalue(e.expressions[i]) + local v,e = getvalue(e.expressions[i]) if e then return nil,e end local key = typ.convertible == "tuple" and i or r.field t[key] = v @@ -923,7 +923,7 @@ function T.quote:asvalue() return t elseif e:is "var" then return e.symbol elseif e:is "luaobject" then - return e.value + return e.value else local runconstantprop = function() return terra.constant(self):get() @@ -963,7 +963,7 @@ function T.Symbol:__tostring() end function T.Symbol:tocname() return "__symbol"..tostring(self.id) end -_G["symbol"] = terra.newsymbol +_G["symbol"] = terra.newsymbol -- LABEL function terra.islabel(l) return T.Label:isclassof(l) end @@ -1013,7 +1013,7 @@ terra.asm = terra.internalmacro(function(diag,tree,returntype, asm, constraints, local args = List{...} return typecheck(newobject(tree, T.inlineasm,returntype:astype(), tostring(asm:asvalue()), not not volatile:asvalue(), tostring(constraints:asvalue()), args)) end) - + local evalluaexpression -- CONSTRUCTORS @@ -1031,7 +1031,7 @@ local function layoutstruct(st,tree,env) end return { field = v.key, type = resolvedtype } end - + local function getrecords(records) return records:map(function(v) if v.kind == "structlist" then @@ -1044,9 +1044,9 @@ local function layoutstruct(st,tree,env) local metatype = tree.metatype and evalluaexpression(env,tree.metatype) st.entries = getrecords(tree.records.entries) st.tree = tree --to track whether the struct has already beend defined - --we keep the tree to improve error reporting + --we keep the tree to improve error reporting st.anchor = tree --replace the anchor generated by newstruct with this struct definition - --this will cause errors on the type to be reported at the definition + --this will cause errors on the type to be reported at the definition if metatype then invokeuserfunction(tree,"invoking metatype function",false,metatype,st) end @@ -1095,7 +1095,7 @@ function terra.defineobjects(fmt,envfn,...) end return t,name:match("[^.]*$") end - + local decls = terralib.newlist() for i,c in ipairs(cmds) do --pass: declare all structs if "s" == c.c then @@ -1159,7 +1159,7 @@ function terra.defineobjects(fmt,envfn,...) end end diag:finishandabortiferrors("Errors reported during function declaration.",2) - + for i,c in ipairs(cmds) do -- pass: define structs if "s" == c.c and c.tree then layoutstruct(decls[i],c.tree,env) @@ -1206,7 +1206,7 @@ end -- TYPE -do +do --returns a function string -> string that makes names unique by appending numbers local function uniquenameset(sep) @@ -1227,7 +1227,7 @@ do local function tovalididentifier(name) return tostring(name):gsub("[^_%w]","_"):gsub("^(%d)","_%1"):gsub("^$","_") --sanitize input to be valid identifier end - + local function memoizefunction(fn) local info = debug.getinfo(fn,'u') local nparams = not info.isvararg and info.nparams @@ -1252,7 +1252,7 @@ do return v end end - + local types = {} local defaultproperties = { "name", "tree", "undefined", "incomplete", "convertible", "cachedcstring", "llvm_definingfunction" } for i,dp in ipairs(defaultproperties) do @@ -1269,9 +1269,9 @@ do end T.Type.__tostring = nil --force override to occur T.Type.__tostring = memoizefunction(function(self) - if self:isstruct() then + if self:isstruct() then if self.metamethods.__typename then - local status,r = pcall(function() + local status,r = pcall(function() return tostring(self.metamethods.__typename(self)) end) if status then return r end @@ -1295,7 +1295,7 @@ do if not self.name then error("unknown type?") end return self.name end) - + T.Type.printraw = terra.printraw function T.Type:isprimitive() return self.kind == "primitive" end function T.Type:isintegral() return self.kind == "primitive" and self.type == "integer" end @@ -1310,20 +1310,20 @@ do function T.Type:ispointertostruct() return self:ispointer() and self.type:isstruct() end function T.Type:ispointertofunction() return self:ispointer() and self.type:isfunction() end function T.Type:isaggregate() return self:isstruct() or self:isarray() end - + function T.Type:iscomplete() return not self.incomplete end - + function T.Type:isvector() return self.kind == "vector" end - + function T.Type:isunit() return types.unit == self end - + local applies_to_vectors = {"isprimitive","isintegral","isarithmetic","islogical", "canbeord"} for i,n in ipairs(applies_to_vectors) do T.Type[n.."orvector"] = function(self) - return self[n](self) or (self:isvector() and self.type[n](self.type)) + return self[n](self) or (self:isvector() and self.type[n](self.type)) end end - + --pretty print of layout of type function T.Type:layoutstring() local seen = {} @@ -1373,7 +1373,7 @@ do if not self[key] then if self[inside] then erroratlocation(self.anchor,erroronrecursion) - else + else self[inside] = true self[key] = getvalue(self) self[inside] = nil @@ -1387,25 +1387,25 @@ do local str = "struct "..nm.." { " local entries = layout.entries for i,v in ipairs(entries) do - + local prevalloc = entries[i-1] and entries[i-1].allocation local nextalloc = entries[i+1] and entries[i+1].allocation - + if v.inunion and prevalloc ~= v.allocation then str = str .. " union { " end - + local keystr = terra.islabel(v.key) and v.key:tocname() or v.key str = str..v.type:cstring().." "..keystr.."; " - + if v.inunion and nextalloc ~= v.allocation then str = str .. " }; " end - + end str = str .. "};" local status,err = pcall(ffi.cdef,str) - if not status then + if not status then if err:match("attempt to redefine") then print(("warning: attempting to define a C struct %s that has already been defined by the luajit ffi, assuming the Terra type matches it."):format(nm)) else error(err) end @@ -1463,7 +1463,7 @@ do elseif self:isstruct() then local nm = uniquecname(tostring(self)) ffi.cdef("typedef struct "..nm.." "..nm..";") --just make a typedef to the opaque type - --when the struct is + --when the struct is self.cachedcstring = nm if self.cachedlayout then definecstruct(nm,self.cachedlayout) @@ -1482,7 +1482,7 @@ do local pow2 = 1 --round N to next power of 2 while pow2 < self.N do pow2 = 2*pow2 end ffi.cdef("typedef "..value.." "..nm.." __attribute__ ((vector_size("..tostring(pow2*elemSz)..")));") - self.cachedcstring = nm + self.cachedcstring = nm elseif self == types.niltype then local nilname = uniquecname("niltype") ffi.cdef("typedef void * "..nilname..";") @@ -1495,13 +1495,13 @@ do error("NYI - cstring") end if not self.cachedcstring then error("cstring not set? "..tostring(self)) end - + --create a map from this ctype to the terra type to that we can implement terra.typeof(cdata) local ctype = ffi.typeof(self.cachedcstring) types.ctypetoterra[tonumber(ctype)] = self local rctype = ffi.typeof(self.cachedcstring.."&") types.ctypetoterra[tonumber(rctype)] = self - + if self:isstruct() then local function index(obj,idx) local method = self:getmethod(idx) @@ -1516,7 +1516,7 @@ do return self.cachedcstring end - + T.struct.getentries = memoizeproperty{ name = "entries"; @@ -1533,7 +1533,7 @@ do end local function checkentry(e,results) if type(e) == "table" then - local f = e.field or e[1] + local f = e.field or e[1] local t = e.type or e[2] if terra.types.istype(t) and (type(f) == "string" or terra.islabel(f)) then results:insert { type = t, field = f} @@ -1545,7 +1545,7 @@ do return end end - erroratlocation(self.anchor,"expected either a field type pair (e.g. { field = , type = } or {,} ), or a list of valid entries representing a union") + erroratlocation(self.anchor,"expected either a field type pair (e.g. { field = , type = } or {,} ), or a list of valid entries representing a union") end local checkedentries = terra.newlist() for i,e in ipairs(entries) do checkentry(e,checkedentries) end @@ -1561,7 +1561,7 @@ do end end T.struct.getlayout = memoizeproperty { - name = "layout"; + name = "layout"; erroronrecursion = "type recursively contains itself, or using a type whose layout failed"; getvalue = function(self) local tree = self.anchor @@ -1569,7 +1569,7 @@ do local nextallocation = 0 local uniondepth = 0 local unionsize = 0 - + local layout = { entries = terra.newlist(), keytoindex = {} @@ -1581,12 +1581,12 @@ do elseif t:isarray() then ensurelayout(t.type) elseif t == types.opaque then - reportopaque(self) + reportopaque(self) end end ensurelayout(t) local entry = { type = t, key = k, allocation = nextallocation, inunion = uniondepth > 0 } - + if layout.keytoindex[entry.key] ~= nil then erroratlocation(tree,"duplicate field ",tostring(entry.key)) end @@ -1621,7 +1621,7 @@ do end end addentrylist(entries) - + dbprint(2,"Resolved Named Struct To:") dbprintraw(2,self) if self.cachedcstring then @@ -1635,7 +1635,7 @@ do self.returntype:complete() return self end - function T.Type:complete() + function T.Type:complete() if self.incomplete then if self:isarray() then self.type:complete() @@ -1647,8 +1647,8 @@ do local layout = self:getlayout() if not layout.invalid then self.incomplete = nil --static initializers run only once - --if one of the members of this struct recursively - --calls complete on this type, then it will return before the static initializer has run + --if one of the members of this struct recursively + --calls complete on this type, then it will return before the static initializer has run for i,e in ipairs(layout.entries) do e.type:complete() end @@ -1666,7 +1666,7 @@ do function T.Type:tcomplete(anchor) return invokeuserfunction(anchor,"finalizing type",false,self.complete,self) end - + local function defaultgetmethod(self,methodname) local fnlike = self.methods[methodname] if not fnlike and terra.ismacro(self.metamethods.__methodmissing) then @@ -1696,14 +1696,14 @@ do function T.struct:getfields() return self:getlayout().entries end - + function types.istype(t) return T.Type:isclassof(t) end - + --map from luajit ffi ctype objects to corresponding terra type types.ctypetoterra = {} - + local function globaltype(name, typ, min_v, max_v) typ.name = typ.name or name rawset(_G,name,typ) @@ -1711,7 +1711,7 @@ do if min_v then function typ:min() return terra.cast(self, min_v) end end if max_v then function typ:max() return terra.cast(self, max_v) end end end - + --initialize integral types local integer_sizes = {1,2,4,8} for _,size in ipairs(integer_sizes) do @@ -1732,23 +1732,23 @@ do local typ = T.primitive("integer",size,s) globaltype(name,typ,min,max) end - end - + end + globaltype("float", T.primitive("float",4,true), -math.huge, math.huge) globaltype("double",T.primitive("float",8,true), -math.huge, math.huge) globaltype("bool", T.primitive("logical",1,false)) - + types.error,T.error.name = T.error,"" T.luaobjecttype.name = "luaobjecttype" - + types.niltype = T.niltype globaltype("niltype",T.niltype) - + types.opaque,T.opaque.incomplete = T.opaque,true globaltype("opaque", T.opaque) - + types.array,types.vector,types.functype = T.array,T.vector,T.functype - + T.functype.incomplete = true function T.functype:init() if self.isvararg and #self.parameters == 0 then error("vararg functions must have at least one concrete parameter") end @@ -1757,18 +1757,18 @@ do function T.array:init() self.incomplete = true end - + function T.vector:init() if not self.type:isprimitive() and self.type ~= T.error then error("vectors must be composed of primitive types (for now...) but found type "..tostring(self.type)) end end - + types.tuple = memoizefunction(function(...) local args = terra.newlist {...} local t = types.newstruct() for i,e in ipairs(args) do - if not types.istype(e) then + if not types.istype(e) then error("expected a type but found "..type(e)) end t.entries:insert {"_"..(i-1),e} @@ -1792,7 +1792,7 @@ do function types.newstructwithanchor(displayname,anchor) assert(displayname ~= "") local name = getuniquestructname(displayname) - local tbl = T.struct(name) + local tbl = T.struct(name) tbl.entries = List() tbl.methods = {} tbl.metamethods = {} @@ -1800,7 +1800,7 @@ do tbl.incomplete = true return tbl end - + function types.funcpointer(parameters,ret,isvararg) if types.istype(parameters) then parameters = {parameters} @@ -1839,7 +1839,7 @@ end -- TYPECHECKER function evalluaexpression(env, e) if not T.luaexpression:isclassof(e) then - error("not a lua expression?") + error("not a lua expression?") end assert(type(e.expression) == "function") local fn = e.expression @@ -1847,7 +1847,7 @@ function evalluaexpression(env, e) setfenv(fn,env) local v = invokeuserfunction(e,"evaluating Lua code from Terra",false,fn) setfenv(fn,oldenv) --otherwise, we hold false reference to env, -- in the case of an error, this function will still hold a reference - -- but without a good way of doing 'finally' without messing with the error trace there is no way around this + -- but without a good way of doing 'finally' without messing with the error trace there is no way around this return v end @@ -1866,7 +1866,7 @@ function evaltype(diag,env,typ) diag:reporterror(typ,"expected a type but found ",terra.type(v)) return terra.types.error end - + function evaluateparameterlist(diag, env, paramlist, requiretypes) local result = List() for i,p in ipairs(paramlist) do @@ -1898,21 +1898,21 @@ function evaluateparameterlist(diag, env, paramlist, requiretypes) if requiretypes and not entry.type then diag:reporterror(entry,"type must be specified for parameters and uninitialized variables") end - + end return result end - + local function semanticcheck(diag,parameters,block) local symbolenv = terra.newenvironment() - + local labelstates = {} -- map from label value to labelstate object, either representing a defined or undefined label - local globalsused = List() - + local globalsused = List() + local loopdepth = 0 local function enterloop() loopdepth = loopdepth + 1 end local function leaveloop() loopdepth = loopdepth - 1 end - + local scopeposition = List() local function getscopeposition() return List { unpack(scopeposition) } end local function getscopedepth(position) @@ -2040,7 +2040,7 @@ local function semanticcheck(diag,parameters,block) end visit(parameters) visit(block) - + --check the label table for any labels that have been referenced but not defined local labeldepths = {} for k,state in pairs(labelstates) do @@ -2050,7 +2050,7 @@ local function semanticcheck(diag,parameters,block) labeldepths[k] = getscopedepth(state.position) end end - + return labeldepths, globalsused end @@ -2058,7 +2058,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local env = terra.newenvironment(luaenv or {}) local diag = terra.newdiagnostics() simultaneousdefinitions = simultaneousdefinitions or {} - + local invokeuserfunction = function(...) diag:finishandabortiferrors("Errors reported during typechecking.",2) return invokeuserfunction(...) @@ -2067,7 +2067,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) diag:finishandabortiferrors("Errors reported during typechecking.",2) return evalluaexpression(...) end - + local function checklabel(e,stringok) if e.kind == "namedident" then return e end local r = evalluaexpression(env:combinedenv(),e.expression) @@ -2133,7 +2133,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local layout = v.type:getlayout(v) local index = layout.keytoindex[field] - + if index == nil then return nil,false end @@ -2165,7 +2165,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif type(v) == "cdata" then local typ = terra.typeof(v) if typ:isaggregate() then --when an aggregate is directly referenced from Terra we get its pointer - --a constant would make an entire copy of the object + --a constant would make an entire copy of the object local ptrobj = createsingle(terra.constant(terra.types.pointer(typ),v)) return insertdereference(ptrobj) end @@ -2233,7 +2233,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end local structvariable, var_ref = allocvar(exp,exp.type,"") - + local entries = List() if #from.entries > #to.entries or (not explicit and #from.entries ~= #to.entries) then err("structural cast invalid, source has ",#from.entries," fields but target has only ",#to.entries) @@ -2260,14 +2260,14 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return exp, true else if ((typ:isprimitive() and exp.type:isprimitive()) or - (typ:isvector() and exp.type:isvector() and typ.N == exp.type.N)) and - not typ:islogicalorvector() and not exp.type:islogicalorvector() then + (typ:isvector() and exp.type:isvector() and typ.N == exp.type.N)) and + not typ:islogicalorvector() and not exp.type:islogicalorvector() then return createcast(exp,typ), true elseif typ:ispointer() and exp.type:ispointer() and typ.type == terra.types.opaque then --implicit cast from any pointer to &opaque return createcast(exp,typ), true elseif typ:ispointer() and exp.type == terra.types.niltype then --niltype can be any pointer return createcast(exp,typ), true - elseif typ:isstruct() and typ.convertible and exp.type:isstruct() and exp.type.convertible then + elseif typ:isstruct() and typ.convertible and exp.type:isstruct() and exp.type.convertible then return structcast(false,exp,typ,speculative), true elseif typ:ispointer() and exp.type:isarray() and typ.type == exp.type.type then return createcast(exp,typ), true @@ -2295,7 +2295,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local success,result = invokeuserfunction(exp, "invoking __cast", true,__cast,exp.type,typ,quotedexp) if success then local result = asterraexpression(exp,result) - if result.type ~= typ then + if result.type ~= typ then diag:reporterror(exp,"user-defined cast returned expression with the wrong type.") end return result,true @@ -2326,22 +2326,22 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end return createcast(exp,typ) elseif (typ:isprimitive() and exp.type:isprimitive()) - or (typ:isvector() and exp.type:isvector() and typ.N == exp.type.N) then --explicit conversions from logicals to other primitives are allowed + or (typ:isvector() and exp.type:isvector() and typ.N == exp.type.N) then --explicit conversions from logicals to other primitives are allowed return createcast(exp,typ) - elseif typ:isstruct() and exp.type:isstruct() and exp.type.convertible then + elseif typ:isstruct() and exp.type:isstruct() and exp.type.convertible then return structcast(true,exp,typ) else return insertcast(exp,typ) --otherwise, allow any implicit casts end end function insertrecievercast(exp,typ,speculative) --casts allow for method recievers a:b(c,d) ==> b(a,c,d), but 'a' has additional allowed implicit casting rules - --type can also be == "vararg" if the expected type of the reciever was an argument to the varargs of a function (this often happens when it is a lua function) - if typ == "vararg" then - return insertaddressof(exp), true - elseif typ:ispointer() and not exp.type:ispointer() then - --implicit address of allowed for recievers - return insertcast(insertaddressof(exp),typ,speculative) - else + --type can also be == "vararg" if the expected type of the reciever was an argument to the varargs of a function (this often happens when it is a lua function) + if typ == "vararg" then + return insertaddressof(exp), true + elseif typ:ispointer() and not exp.type:ispointer() then + --implicit address of allowed for recievers + return insertcast(insertaddressof(exp),typ,speculative) + else return insertcast(exp,typ,speculative) end --notes: @@ -2407,7 +2407,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) entries:insert(rt) end return terra.types.tuple(unpack(entries)) - else + else err() return terra.types.error end @@ -2425,7 +2425,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return e:aserror() end return ee:copy { operands = List{e} }:withtype(e.type) - end + end local function meetbinary(e,property,lhs,rhs) @@ -2452,9 +2452,9 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if #operands == 1 then return checkunary(e,operands,"isarithmeticorvector") end - + local l,r = unpack(operands) - + local function pointerlike(t) return t:ispointer() or t:isarray() end @@ -2505,15 +2505,15 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else typ = a.type end - + a = insertcast(a,typ) b = insertcast(b,typ) - + else diag:reporterror(ee,"arguments to shift must be integers but found ",a.type," and ", b.type) end end - + return ee:copy { operands = List{a,b} }:withtype(typ) end @@ -2527,7 +2527,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) diag:reporterror(ee,"conditional in select is not the same shape as ",cond.type) end elseif cond.type ~= terra.types.bool then - diag:reporterror(ee,"expected a boolean or vector of booleans but found ",cond.type) + diag:reporterror(ee,"expected a boolean or vector of booleans but found ",cond.type) end end return ee:copy {operands = List {cond,l,r}}:withtype(t) @@ -2556,7 +2556,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local function checkoperator(ee) local op_string = ee.operator - + --check non-overloadable operators first if op_string == "@" then local e = checkexp(ee.operands[1]) @@ -2566,16 +2566,16 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local ty = terra.types.pointer(e.type) return ee:copy { operands = List {e} }:withtype(ty) end - + local op, genericoverloadmethod, unaryoverloadmethod = unpack(operator_table[op_string] or {}) - + if op == nil then diag:reporterror(ee,"operator ",op_string," not defined in terra code.") return ee:aserror() end - + local operands = ee.operands:map(checkexp) - + local overloads = terra.newlist() for i,e in ipairs(operands) do if e.type:isstruct() then @@ -2586,7 +2586,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) end end end - + if #overloads > 0 then return checkcall(ee, overloads, operands, "all", true, "expression") end @@ -2595,7 +2595,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) --functions to handle typecheck invocations (functions,methods,macros,operator overloads) local function removeluaobject(e) - if not e:is "luaobject" or e.type == terra.types.error then + if not e:is "luaobject" or e.type == terra.types.error then return e --don't repeat error messages else if terra.types.istype(e.value) then @@ -2646,7 +2646,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local function tryinsertcasts(anchor, typelists,castbehavior, speculate, allowambiguous, paramlist) local PERFECT_MATCH,CAST_MATCH,TOP = 1,2,math.huge - + local function trylist(typelist, speculate) if #typelist ~= #paramlist then if not speculate then @@ -2726,7 +2726,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if #results > 1 and not allowambiguous then local strings = results:map(function(x) return mkstring(typelists[x.idx],"type list (",",",") ") end) diag:reporterror(anchor,"call to overloaded function is ambiguous. can apply to ",unpack(strings)) - end + end return results[1].expressions, results[1].idx end end @@ -2871,15 +2871,20 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local function checkmetacopyassignment(anchor, from, to) if not terralib.ext then return end + local function tryaddmissingcopy(tp) + if tp and tp:isstruct() then + if not tp.methods.__copy then + terralib.ext.addmissing.__copy(tp) + end + end + end + --check for __copy and generate it if need be or exit early local ftype, ttype = from.type, to.type if (ftype and ftype:isstruct()) or (ttype and ttype:isstruct()) then - --case of equal struct types + tryaddmissingcopy(ftype) + tryaddmissingcopy(ttype) 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 + --equal types - exit early if not (ftype.methods.__copy) then return end else --otherwise @@ -2930,11 +2935,11 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if location == "lexpression" and typ.metamethods.__update then local function setter(rhs) arguments:insert(rhs) - return checkmethodwithreciever(exp, true, "__update", fnlike, arguments, "statement") + return checkmethodwithreciever(exp, true, "__update", fnlike, arguments, "statement") end return newobject(exp,T.setteru,setter) end - return checkmethodwithreciever(exp, true, "__apply", fnlike, arguments, location) + return checkmethodwithreciever(exp, true, "__apply", fnlike, arguments, location) end end return checkcall(exp, terra.newlist { fnlike } , arguments, "none", false, location) @@ -2942,8 +2947,8 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) function checkcall(anchor, fnlikelist, arguments, castbehavior, allowambiguous, location) --arguments are always typed trees, or a lua object assert(#fnlikelist > 0) - - --collect all the terra functions, stop collecting when we reach the first + + --collect all the terra functions, stop collecting when we reach the first --macro and record it as themacro local terrafunctions = terra.newlist() local themacro = nil @@ -2977,14 +2982,14 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if fn.type ~= terra.types.error then diag:reporterror(anchor,"expected a function but found ",fn.type) end - end + end end local function createcall(callee, paramlist) callee.type.type:tcompletefunction(anchor) return newobject(anchor,T.apply,callee,paramlist):withtype(callee.type.type.returntype) end - + if #terrafunctions > 0 then local paramlist = arguments:map(removeluaobject) local function getparametertypes(fn) --get the expected types for parameters to the call (this extends the function type to the length of the parameters if the function is vararg) @@ -3050,7 +3055,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local v = checkexp(e.value,"luavalue") local f = checklabel(e.field,true) local field = f.value - + if v:is "luaobject" then -- handle A.B where A is a luatable or type --check for and handle Type.staticmethod if terra.types.istype(v.value) and v.value:isstruct() then @@ -3072,7 +3077,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return asterraexpression(e,selected,location) end end - + if v.type:ispointertostruct() then --allow 1 implicit dereference v = insertdereference(v) end @@ -3082,12 +3087,12 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if not success then --struct has no member field, call metamethod __entrymissing local typ = v.type - + local function checkmacro(metamethod,arguments,location) local named = terra.internalmacro(function(ctx,tree,...) return typ.metamethods[metamethod]:run(ctx,tree,field,...) end) - local getter = asterraexpression(e, named, "luaobject") + local getter = asterraexpression(e, named, "luaobject") return checkcall(v, terra.newlist{ getter }, arguments, "first", false, location) end if location == "lexpression" and typ.metamethods.__setentry then @@ -3117,7 +3122,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif e:is "index" then local v = checkexp(e.value) local idx = checkexp(e.index) - local typ,lvalue = terra.types.error, v.type:ispointer() or (v.type:isarray() and v.lvalue) + local typ,lvalue = terra.types.error, v.type:ispointer() or (v.type:isarray() and v.lvalue) if v.type:ispointer() or v.type:isarray() or v.type:isvector() then typ = v.type.type if not idx.type:isintegral() and idx.type ~= terra.types.error then @@ -3138,7 +3143,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif e:is "vectorconstructor" or e:is "arrayconstructor" then local entries = checkexpressions(e.expressions) local N = #entries - + local typ if e.oftype ~= nil then typ = e.oftype:tcomplete(e) @@ -3147,14 +3152,14 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) diag:reporterror(e,"cannot determine type of empty aggregate") return e:aserror() end - + --figure out what type this vector has typ = entries[1].type for i,e2 in ipairs(entries) do typ = typemeet(e,typ,e2.type) end end - + local aggtype if e:is "vectorconstructor" then if not typ:isprimitive() and typ ~= terra.types.error then @@ -3165,7 +3170,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else aggtype = terra.types.array(typ,N) end - + --insert the casts to the right type in the parameter list local typs = entries:map(function(x) return typ end) entries = insertcasts(e,typs,entries) @@ -3194,8 +3199,8 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return e:aserror() end if not (addr.type.type:isintegral() or addr.type.type:ispointer()) then - diag:reporterror(e,"for cmpxchg address must be a pointer to an integral or pointer type, but found ", addr.type.type) - return e:aserror() + diag:reporterror(e,"for cmpxchg address must be a pointer to an integral or pointer type, but found ", addr.type.type) + return e:aserror() end local cmp = insertcast(checkexp(e.cmp),addr.type.type) local new = insertcast(checkexp(e.new),addr.type.type) @@ -3207,20 +3212,20 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return e:aserror() end if e.operator == "xchg" then - if not (addr.type.type:isintegral() or addr.type.type:isfloat()) then - diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to an integral or floating point type, but found ", addr.type.type) - return e:aserror() - end + if not (addr.type.type:isintegral() or addr.type.type:isfloat()) then + diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to an integral or floating point type, but found ", addr.type.type) + return e:aserror() + end elseif e.operator == "fadd" or e.operator == "fsub" or e.operator == "fmax" or e.operator == "fmin" then - if not addr.type.type:isfloat() then - diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to a floating point type, but found ", addr.type.type) - return e:aserror() - end + if not addr.type.type:isfloat() then + diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to a floating point type, but found ", addr.type.type) + return e:aserror() + end else - if not (addr.type.type:isintegral() or addr.type.type:ispointer()) then - diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to an integral or pointer type, but found ", addr.type.type) - return e:aserror() - end + if not (addr.type.type:isintegral() or addr.type.type:ispointer()) then + diag:reporterror(e,"for operator " .. e.operator .. " address must be a pointer to an integral or pointer type, but found ", addr.type.type) + return e:aserror() + end end local value = insertcast(checkexp(e.value),addr.type.type) return e:copy { address = addr, value = value }:withtype(addr.type.type) @@ -3232,7 +3237,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local ns = checkstmts(e.statements) local ne = checkexpressions(e.expressions) return createlet(e,ns,ne,e.hasstatements) - elseif e:is "constructoru" then + elseif e:is "constructoru" then local paramlist = terra.newlist() local named = 0 for i,f in ipairs(e.records) do @@ -3266,7 +3271,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return e:aserror() end end - + local result = docheck(e_) --freeze all types returned by the expression (or list of expressions) if not result:is "luaobject" and not result:is "setteru" then @@ -3278,7 +3283,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) if location ~= "luavalue" then result = removeluaobject(result) end - + return result end @@ -3352,7 +3357,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) 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 r:is "var" or r:is "literal" or r:is "constant" or r:is "select" or r:is "structcast" then if checkmetacopyassignment(anchor, r, lhs[i]) then cpassign = true end @@ -3505,7 +3510,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkblock(s) elseif s:is "returnstat" then return s:copy { expression = checkexp(s.expression)} - elseif s:is "label" or s:is "gotostat" then + elseif s:is "label" or s:is "gotostat" then local ss = checklabel(s.label) return copyobject(s, { label = ss }) elseif s:is "breakstat" then @@ -3514,7 +3519,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return checkcondbranch(s) elseif s:is "fornumu" then local initial, limit, step = checkexp(s.initial), checkexp(s.limit), s.step and checkexp(s.step) - local t = typemeet(initial,initial.type,limit.type) + local t = typemeet(initial,initial.type,limit.type) t = step and typemeet(limit,t,step.type) or t local variables = checkformalparameterlist(List {s.variable },false) if #variables ~= 1 then @@ -3529,7 +3534,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return newobject(s,T.fornum,variable,initial,limit,step,body) elseif s:is "forlist" then local iterator = checkexp(s.iterator) - + local typ = iterator.type if typ:ispointertostruct() then typ,iterator = typ.type, insertdereference(iterator) @@ -3539,7 +3544,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return s end local generator = typ.metamethods.__for - + local function bodycallback(...) local exps = List() for i = 1,select("#",...) do @@ -3554,7 +3559,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local stats = createstatementlist(s, List { assign, body }) return terra.newquote(stats) end - + local value = invokeuserfunction(s, "invoking __for", false ,generator,terra.newquote(iterator), bodycallback) return asterraexpression(s,value,"statement") elseif s:is "ifstat" then @@ -3614,7 +3619,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) else newstats:insert(s) end - end + end for _,s in ipairs(stmts) do local r = checksingle(s) if r.kind == "statlist" then -- lists of statements are spliced directly into the list @@ -3701,7 +3706,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) local typed_parameters = checkformalparameterlist(topexp.parameters, true) local parameter_types = typed_parameters:map("type") local body,returntype = checkreturns(checkblock(topexp.body),topexp.returntype) - + local fntype = terra.types.functype(parameter_types,returntype,topexp.is_varargs):tcompletefunction(topexp) diag:finishandabortiferrors("Errors reported during typechecking.",2) local labeldepths,globalsused = semanticcheck(diag,typed_parameters,body) @@ -3743,7 +3748,7 @@ function terra.registerinternalizedfiles(names,contents,sizes) cur.children = cur.children or {} cur.kind = "directory" if not cur.children[segment] then - cur.children[segment] = {} + cur.children[segment] = {} end cur = cur.children[segment] end @@ -3779,18 +3784,18 @@ function terra.includecstring(code,cargs,target) args:insert(clangresourcedirectory.."/include") end for _,path in ipairs(terra.systemincludes) do - args:insert("-internal-isystem") - args:insert(path) + args:insert("-internal-isystem") + 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) + 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") + args:insert("-fgnuc-version=4.2.1") end if cargs then @@ -3824,26 +3829,26 @@ end -- GLOBAL MACROS terra.sizeof = terra.internalmacro( -function(diag,tree,typ) - return typecheck(newobject(tree,T.sizeof,typ:astype())) -end, -function (terratype,...) - terratype:complete() - return terra.llvmsizeof(terra.jitcompilationunit,terratype) -end + function(diag,tree,typ) + return typecheck(newobject(tree,T.sizeof,typ:astype())) + end, + function (terratype,...) + terratype:complete() + return terra.llvmsizeof(terra.jitcompilationunit,terratype) + end ) _G["sizeof"] = terra.sizeof _G["vector"] = terra.internalmacro( -function(diag,tree,...) - if not diag then - error("nil first argument in vector constructor") - end - if not tree then - error("nil second argument in vector constructor") - end - return typecheck(newobject(tree,T.vectorconstructor,nil,List{...})) -end, -terra.types.vector + function(diag,tree,...) + if not diag then + error("nil first argument in vector constructor") + end + if not tree then + error("nil second argument in vector constructor") + end + return typecheck(newobject(tree,T.vectorconstructor,nil,List{...})) + end, + terra.types.vector ) _G["vectorof"] = terra.internalmacro(function(diag,tree,typ,...) return typecheck(newobject(tree,T.vectorconstructor,typ:astype(),List{...})) @@ -3866,7 +3871,7 @@ local function createunpacks(tupleonly) local entries = typ:getentries() from = from and tonumber(from:asvalue()) or 1 to = to and tonumber(to:asvalue()) or #entries - for i = from,to do + for i = from,to do local e= entries[i] if e.field then local ident = newobject(tree,type(e.field) == "string" and T.namedident or T.labelident,e.field) @@ -3877,8 +3882,8 @@ local function createunpacks(tupleonly) end local function unpacklua(cdata,from,to) local t = type(cdata) == "cdata" and terra.typeof(cdata) - if not t or not t:isstruct() or (tupleonly and t.convertible ~= "tuple") then - return cdata + if not t or not t:isstruct() or (tupleonly and t.convertible ~= "tuple") then + return cdata end local results = terralib.newlist() local entries = t:getentries() @@ -3917,9 +3922,9 @@ local function createattributetable(q) if attr.align ~= nil and type(attr.align) ~= "number" then error("align attribute must be a number, not a " .. type(attr.align)) end - return T.attr(attr.nontemporal and true or false, - attr.align or nil, - attr.isvolatile and true or false) + return T.attr(attr.nontemporal and true or false, + attr.align or nil, + attr.isvolatile and true or false) end terra.attrload = terra.internalmacro( function(diag,tree,addr,attr) @@ -3951,8 +3956,8 @@ local function createfenceattributetable(q) error("ordering attribute must be a string, not a " .. type(attr.ordering)) end return T.fenceattr( - attr.syncscope or nil, - attr.ordering or nil) + attr.syncscope or nil, + attr.ordering or nil) end terra.fence = terra.internalmacro( function(diag,tree,attr) @@ -3986,12 +3991,12 @@ local function createcmpxchgattributetable(q) error("align attribute must be a number, not a " .. type(attr.align)) end return T.cmpxchgattr( - attr.syncscope or nil, - attr.success_ordering or nil, - attr.failure_ordering or nil, - attr.align or nil, - attr.isvolatile and true or false, - attr.isweak and true or false) + attr.syncscope or nil, + attr.success_ordering or nil, + attr.failure_ordering or nil, + attr.align or nil, + attr.isvolatile and true or false, + attr.isweak and true or false) end terra.cmpxchg = terra.internalmacro( function(diag,tree,addr,cmp,new,attr) @@ -4019,10 +4024,10 @@ local function createatomicattributetable(q) error("align attribute must be a number, not a " .. type(attr.align)) end return T.atomicattr( - attr.syncscope or nil, - attr.ordering or nil, - attr.align or nil, - attr.isvolatile and true or false) + attr.syncscope or nil, + attr.ordering or nil, + attr.align or nil, + attr.isvolatile and true or false) end terra.atomicrmw = terra.internalmacro( function(diag,tree,op,addr,value,attr) @@ -4031,7 +4036,7 @@ terra.atomicrmw = terra.internalmacro( function(diag,tree,op,addr,value,attr) end local op_value = op:asvalue() if type(op_value) ~= "string" then - error("operator argument to atomicrmw must be a string, not a " .. type(op_value)) + error("operator argument to atomicrmw must be a string, not a " .. type(op_value)) end return typecheck(newobject(tree,T.atomicrmw,op_value,addr,value,createatomicattributetable(attr))) end) @@ -4045,7 +4050,7 @@ function prettystring(toptree,breaklines) local buffer = terralib.newlist() -- list of strings that concat together into the pretty output local env = terra.newenvironment({}) local indentstack = terralib.newlist{ 0 } -- the depth of each indent level - + local currentlinelength = 0 local function enterblock() indentstack:insert(indentstack[#indentstack] + 4) @@ -4059,7 +4064,7 @@ function prettystring(toptree,breaklines) local function emit(fmt,...) local function toformat(x) if type(x) ~= "number" and type(x) ~= "string" then - return tostring(x) + return tostring(x) else return x end @@ -4075,11 +4080,11 @@ function prettystring(toptree,breaklines) end local function differentlocation(a,b) return (a.linenumber ~= b.linenumber or a.filename ~= b.filename) - end + end local lastanchor = { linenumber = "", filename = "" } local function begin(anchor,...) local fname = differentlocation(lastanchor,anchor) and (anchor.filename..":"..anchor.linenumber..": ") - or "" + or "" emit("%s",pad(fname,24)) currentlinelength = 0 emit((" "):rep(indentstack[#indentstack])) @@ -4127,10 +4132,10 @@ function prettystring(toptree,breaklines) end local function emitParam(p) assert(T.allocvar:isclassof(p) or T.param:isclassof(p)) - if T.unevaluatedparam:isclassof(p) then + if T.unevaluatedparam:isclassof(p) then emit("%s%s",IdentToString(p.name),p.type and " : "..luaexpression or "") else - emitIdent(p.name,p.symbol) + emitIdent(p.name,p.symbol) if p.type then emit(" : %s",p.type) end end end @@ -4194,7 +4199,7 @@ function prettystring(toptree,breaklines) begin(s,"for ") emitParam(s.variable) emit(" = ") - emitExp(s.initial) emit(",") emitExp(s.limit) + emitExp(s.initial) emit(",") emitExp(s.limit) if s.step then emit(",") emitExp(s.step) end emit(" do\n") emitStmt(s.body) @@ -4264,7 +4269,7 @@ function prettystring(toptree,breaklines) emit("\n") end end - + local function makeprectable(...) local lst = {...} local sz = #lst @@ -4276,13 +4281,13 @@ function prettystring(toptree,breaklines) end local prectable = makeprectable( - "+",7,"-",7,"*",8,"/",8,"%",8, - "^",11,"..",6,"<<",4,">>",4, - "==",3,"<",3,"<=",3, - "~=",3,">",3,">=",3, - "and",2,"or",1, - "@",9,"&",9,"not",9,"select",12) - + "+",7,"-",7,"*",8,"/",8,"%",8, + "^",11,"..",6,"<<",4,">>",4, + "==",3,"<",3,"<=",3, + "~=",3,">",3,">=",3, + "and",2,"or",1, + "@",9,"&",9,"not",9,"select",12) + local function getprec(e) if e:is "operator" then if "-" == e.operator and #e.operands == 1 then return 9 --unary minus case @@ -4381,7 +4386,7 @@ function prettystring(toptree,breaklines) emit(")") elseif e:is "constructor" then local success,keys = pcall(function() return e.type:getlayout().entries:map(function(e) return tostring(e.key) end) end) - if not success then emit(" = ") + if not success then emit(" = ") else emitList(keys,"",", "," = ",emit) end emitParamList(e.expressions) elseif e:is "constructoru" then @@ -4451,11 +4456,11 @@ function prettystring(toptree,breaklines) emit("",tostring(e.value)) end elseif e:is "method" then - doparens(e,e.value) - emit(":%s",IdentToString(e.name)) - emit("(") - emitParamList(e.arguments) - emit(")") + doparens(e,e.value) + emit(":%s",IdentToString(e.name)) + emit("(") + emitParamList(e.arguments) + emit(")") elseif e:is "debuginfo" then emit("debuginfo(%q,%d)",e.customfilename,e.customlinenumber) elseif e:is "inlineasm" then @@ -4575,12 +4580,12 @@ function terra.saveobj(filename,filekind,env,arguments,target,optimize) end local profile if optimize == nil or type(optimize) == "boolean" then - profile = {} + profile = {} elseif type(optimize) == "table" then - profile = optimize - optimize = optimize["optimize"] + profile = optimize + optimize = optimize["optimize"] else - error("expected optimize to be a boolean or table but found " .. type(optimize)) + error("expected optimize to be a boolean or table but found " .. type(optimize)) end local cu = terra.newcompilationunit(target or terra.nativetarget,false,profile) @@ -4596,27 +4601,27 @@ end -- configure path variables if ffi.os == "Windows" then - terra.cudahome = os.getenv("CUDA_PATH") + terra.cudahome = os.getenv("CUDA_PATH") else - terra.cudahome = os.getenv("CUDA_HOME") or "/usr/local/cuda" + terra.cudahome = os.getenv("CUDA_HOME") or "/usr/local/cuda" end -terra.cudalibpaths = ({ OSX = {driver = "/usr/local/cuda/lib/libcuda.dylib", runtime = "$CUDA_HOME/lib/libcudart.dylib", nvvm = "$CUDA_HOME/nvvm/lib/libnvvm.dylib"}; - Linux = {driver = "libcuda.so", runtime = "$CUDA_HOME/lib64/libcudart.so", nvvm = "$CUDA_HOME/nvvm/lib64/libnvvm.so"}; - Windows = {driver = "nvcuda.dll", runtime = "$CUDA_HOME\\bin\\cudart64_*.dll", nvvm = "$CUDA_HOME\\nvvm\\bin\\nvvm64_*.dll"}; })[ffi.os] +terra.cudalibpaths = ({ OSX = {driver = "/usr/local/cuda/lib/libcuda.dylib", runtime = "$CUDA_HOME/lib/libcudart.dylib", nvvm = "$CUDA_HOME/nvvm/lib/libnvvm.dylib"}; + Linux = {driver = "libcuda.so", runtime = "$CUDA_HOME/lib64/libcudart.so", nvvm = "$CUDA_HOME/nvvm/lib64/libnvvm.so"}; + Windows = {driver = "nvcuda.dll", runtime = "$CUDA_HOME\\bin\\cudart64_*.dll", nvvm = "$CUDA_HOME\\nvvm\\bin\\nvvm64_*.dll"}; })[ffi.os] -- OS's that are not supported by CUDA will have an undefined value here if terra.cudalibpaths and terra.cudahome then - for name,path in pairs(terra.cudalibpaths) do - path = path:gsub("%$CUDA_HOME",terra.cudahome) - if path:match("%*") and ffi.os == "Windows" then - local F = io.popen(('dir /b /s "%s" 2> nul'):format(path)) - if F then - path = F:read("*line") or path - F:close() - end - end - terra.cudalibpaths[name] = path - end -end + for name,path in pairs(terra.cudalibpaths) do + path = path:gsub("%$CUDA_HOME",terra.cudahome) + if path:match("%*") and ffi.os == "Windows" then + local F = io.popen(('dir /b /s "%s" 2> nul'):format(path)) + if F then + path = F:read("*line") or path + F:close() + end + end + terra.cudalibpaths[name] = path + end +end local cudatarget function terra.getcudatarget() @@ -4638,108 +4643,108 @@ if ffi.os == "Windows" then terra.vclinker = ([[%sbin\Host%s\%s\link.exe]]):format(terra.vshome, os.getenv("VSCMD_ARG_HOST_ARCH"), os.getenv("VSCMD_ARG_TGT_ARCH")) end terra.includepath = os.getenv("INCLUDE") - + function terra.getvclinker(target) - local vclib = os.getenv("LIB") - local vcpath = terra.vcpath or os.getenv("Path") - vclib,vcpath = "LIB="..vclib,"Path="..vcpath - return terra.vclinker,vclib,vcpath + local vclib = os.getenv("LIB") + local vcpath = terra.vcpath or os.getenv("Path") + vclib,vcpath = "LIB="..vclib,"Path="..vcpath + return terra.vclinker,vclib,vcpath end else local function compareversion(form, a, b) - if (a == nil) or (b == nil) then return true end - - local alist = {} - for e in string.gmatch(a, form) do table.insert(alist, tonumber(e)) end - local blist = {} - for e in string.gmatch(b, form) do table.insert(blist, tonumber(e)) end - - for i=1,#alist do - if alist[i] ~= blist[i] then - return alist[i] > blist[i] - end - end - return false - end - + if (a == nil) or (b == nil) then return true end + + local alist = {} + for e in string.gmatch(a, form) do table.insert(alist, tonumber(e)) end + local blist = {} + for e in string.gmatch(b, form) do table.insert(blist, tonumber(e)) end + + for i=1,#alist do + if alist[i] ~= blist[i] then + return alist[i] > blist[i] + end + end + return false + end + -- First find the latest Windows SDK installed using the registry local installedroots = [[SOFTWARE\Microsoft\Windows Kits\Installed Roots]] local windowsdk = terra.queryregvalue(installedroots, "KitsRoot10") if windowsdk == nil then - windowsdk = terra.queryregvalue(installedroots, "KitsRoot81") - if windowsdk == nil then - error "Can't find windows SDK version 8.1 or 10! Try running Terra in a Native Tools Developer Console instead." - end - - local version = nil - for i, v in ipairs(terra.listsubdirectories(windowsdk .. "lib")) do - if compareversion("%d+", v, version) then - version = v - end - end - if version == nil then - error "Can't find valid version subdirectory in the SDK! Is the Windows 8.1 SDK installation corrupt?" - end - - terra.sdklib = windowsdk .. "lib\\" .. version + windowsdk = terra.queryregvalue(installedroots, "KitsRoot81") + if windowsdk == nil then + error "Can't find windows SDK version 8.1 or 10! Try running Terra in a Native Tools Developer Console instead." + end + + local version = nil + for i, v in ipairs(terra.listsubdirectories(windowsdk .. "lib")) do + if compareversion("%d+", v, version) then + version = v + end + end + if version == nil then + error "Can't find valid version subdirectory in the SDK! Is the Windows 8.1 SDK installation corrupt?" + end + + terra.sdklib = windowsdk .. "lib\\" .. version else - -- Find highest version. For sanity reasons, we assume the same version folders are in both lib/ and include/ - local version = nil - for i, v in ipairs(terra.listsubdirectories(windowsdk .. "include")) do - if compareversion("%d+", v, version) then - version = v - end - end - if version == nil then - error "Can't find valid version subdirectory in the SDK! Is the SDK installation corrupt?" - end - - terra.sdklib = windowsdk .. "lib\\" .. version - end - + -- Find highest version. For sanity reasons, we assume the same version folders are in both lib/ and include/ + local version = nil + for i, v in ipairs(terra.listsubdirectories(windowsdk .. "include")) do + if compareversion("%d+", v, version) then + version = v + end + end + if version == nil then + error "Can't find valid version subdirectory in the SDK! Is the SDK installation corrupt?" + end + + terra.sdklib = windowsdk .. "lib\\" .. version + end + terra.vshome = terra.findvisualstudiotoolchain() - if terra.vshome == nil then - terra.vshome = terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0]], "ShellFolder") or - terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\12.0]], "ShellFolder") or - terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\11.0]], "ShellFolder") or - terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\10.0]], "ShellFolder") - - if terra.vshome == nil then - error "Can't find Visual Studio either via COM or the registry! Try running Terra in a Native Tools Developer Console instead." - end - terra.vshome = terra.vshome .. "VC\\" - terra.vsarch64 = "amd64" -- Before 2017, Visual Studio had it's own special architecture convention, because who needs standards - terra.vslinkpath = function(host, target) - if string.lower(host) == string.lower(target) then - return ([[BIN\%s\]]):format(host) - else - return ([[BIN\%s_%s\]]):format(host, target) + if terra.vshome == nil then + terra.vshome = terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\12.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\11.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\10.0]], "ShellFolder") + + if terra.vshome == nil then + error "Can't find Visual Studio either via COM or the registry! Try running Terra in a Native Tools Developer Console instead." + end + terra.vshome = terra.vshome .. "VC\\" + terra.vsarch64 = "amd64" -- Before 2017, Visual Studio had it's own special architecture convention, because who needs standards + terra.vslinkpath = function(host, target) + if string.lower(host) == string.lower(target) then + return ([[BIN\%s\]]):format(host) + else + return ([[BIN\%s_%s\]]):format(host, target) + end end - end else - if terra.vshome[#terra.vshome] ~= '\\' then - terra.vshome = terra.vshome .. "\\" - end - terra.vsarch64 = "x64" - terra.vslinkpath = function(host, target) return ([[bin\Host%s\%s\]]):format(host, target) end + if terra.vshome[#terra.vshome] ~= '\\' then + terra.vshome = terra.vshome .. "\\" + end + terra.vsarch64 = "x64" + terra.vslinkpath = function(host, target) return ([[bin\Host%s\%s\]]):format(host, target) end end - + function terra.getvclinker(target) --get the linker, and guess the needed environment variables for Windows if they are not set ... target = target or "x86_64" local winarch = target if target == "x86_64" then - target = terra.vsarch64 - winarch = "x64" -- Unbelievably, Visual Studio didn't follow the Windows SDK convention until 2017+ + target = terra.vsarch64 + winarch = "x64" -- Unbelievably, Visual Studio didn't follow the Windows SDK convention until 2017+ elseif target == "aarch64" or target == "aarch64_be" then - target = "arm64" - winarch = "arm64" + target = "arm64" + winarch = "arm64" end - + local host = ffi.arch if host == "x64" then - host = terra.vsarch64 + host = terra.vsarch64 end - + local linker = terra.vshome..terra.vslinkpath(host, target).."link.exe" local vclib = ([[%s\um\%s;%s\ucrt\%s;]]):format(terra.sdklib, winarch, terra.sdklib, winarch) .. ([[%sLIB\%s;%sATLMFC\LIB\%s;]]):format(terra.vshome, target, terra.vshome, target) local vcpath = terra.vcpath or (os.getenv("Path") or "")..";"..terra.vshome..[[BIN;]]..terra.vshome..terra.vslinkpath(host, host)..";" -- deals with VS2017 cross-compile nonsense: https://github.com/rust-lang/rust/issues/31063 @@ -4757,7 +4762,7 @@ end local defaultterrahome = ffi.os == "Windows" and "C:\\Program Files\\terra" or "/usr/local" terra.terrahome = os.getenv("TERRA_HOME") or terra.terrahome or defaultterrahome local terradefaultpath = ffi.os == "Windows" and ";.\\?.t;"..terra.terrahome.."\\include\\?.t;" - or ";./?.t;"..terra.terrahome.."/share/terra/?.t;" + or ";./?.t;"..terra.terrahome.."/share/terra/?.t;" package.terrapath = (os.getenv("TERRA_PATH") or ";;"):gsub(";;",terradefaultpath) @@ -4908,7 +4913,7 @@ function terra.linkllvmstring(str,target) return terra.linkllvm(str,target,true) terra.languageextension = { tokentype = {}; --metatable for tokentype objects - tokenkindtotoken = {}; --map from token's kind id (terra.kind.name), to the singleton table (terra.languageextension.name) + tokenkindtotoken = {}; --map from token's kind id (terra.kind.name), to the singleton table (terra.languageextension.name) } function terra.importlanguage(languages,entrypoints,langstring) @@ -4917,7 +4922,7 @@ function terra.importlanguage(languages,entrypoints,langstring) if not lang or type(lang) ~= "table" then error("expected a table to define language") end lang.name = lang.name or "anonymous" local function haslist(field,typ) - if not lang[field] then + if not lang[field] then error(field .. " expected to be list of "..typ) end for i,k in ipairs(lang[field]) do @@ -4928,7 +4933,7 @@ function terra.importlanguage(languages,entrypoints,langstring) end haslist("keywords","string") haslist("entrypoints","string") - + for i,e in ipairs(lang.entrypoints) do if entrypoints[e] then error(("language '%s' uses entrypoint '%s' already defined by language '%s'"):format(lang.name,e,entrypoints[e].name),-1) @@ -4974,7 +4979,7 @@ end function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstatement,islocal) local lex = {} - + lex.name = terra.languageextension.name lex.string = terra.languageextension.string lex.number = terra.languageextension.number @@ -5009,7 +5014,7 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme return v end local function doembeddedcode(self,isterra,isexp) - self._cur,self._lookahead = nil,nil --parsing an expression invalidates our lua representations + self._cur,self._lookahead = nil,nil --parsing an expression invalidates our lua representations local expr = embeddedcode(isterra,isexp) return function(env) local oldenv = getfenv(expr) @@ -5036,7 +5041,7 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme function lex:typetostring(name) return name end - + function lex:nextif(typ) if self:cur().type == typ then return self:next() @@ -5057,13 +5062,13 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme end function lex:error(msg) error(msg,0) --,0 suppresses the addition of line number information, which we do not want here since - --this is a user-caused errors + --this is a user-caused errors end function lex:errorexpected(what) self:error(what.." expected") end function lex:expectmatch(typ,openingtokentype,linenumber) - local n = self:nextif(typ) + local n = self:nextif(typ) if not n then if self:cur().linenumber == linenumber then lex:errorexpected(tostring(typ)) @@ -5084,7 +5089,7 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme else lex:error("unexpected token") end - + if not constructor or type(constructor) ~= "function" then error("expected language to return a construction function") end @@ -5094,9 +5099,9 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme return b == 1 and e == string.len(str) end - --fixup names + --fixup names - if not names then + if not names then names = {} end @@ -5130,26 +5135,26 @@ function terra.runlanguage(lang,cur,lookahead,next,embeddedcode,source,isstateme end _G["operator"] = terra.internalmacro(function(diag,anchor,op,...) - local tbl = { - __sub = "-"; - __add = "+"; - __mul = "*"; - __div = "/"; - __mod = "%"; - __lt = "<"; - __le = "<="; - __gt = ">"; - __ge = ">="; - __eq = "=="; - __ne = "~="; - __and = "and"; - __or = "or"; - __not = "not"; - __xor = "^"; - __lshift = "<<"; - __rshift = ">>"; - __select = "select"; - } + local tbl = { + __sub = "-"; + __add = "+"; + __mul = "*"; + __div = "/"; + __mod = "%"; + __lt = "<"; + __le = "<="; + __gt = ">"; + __ge = ">="; + __eq = "=="; + __ne = "~="; + __and = "and"; + __or = "or"; + __not = "not"; + __xor = "^"; + __lshift = "<<"; + __rshift = ">>"; + __select = "select"; + } local opv = op:asvalue() opv = tbl[opv] or opv --operator can be __add or + return typecheck(newobject(anchor,T.operator,opv,List{...})) From bed8cf0d3b824a75ff0ca84849c7de04c488678a Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 22 Nov 2024 16:44:53 +0100 Subject: [PATCH 73/81] fixed test to conform to added code of previous bugfix. one should be careful not to call the copyconstructor if that's not the idea. --- tests/raii-copyctr-cast.t | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/raii-copyctr-cast.t b/tests/raii-copyctr-cast.t index ca31c9031..d08c09ae0 100644 --- a/tests/raii-copyctr-cast.t +++ b/tests/raii-copyctr-cast.t @@ -40,10 +40,11 @@ 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 + var tmp : A + tmp.data = @exp + in + &tmp + end end end From 264229038571a1f5ba9a42bb6b0eaa7e4a773322 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Fri, 22 Nov 2024 16:45:44 +0100 Subject: [PATCH 74/81] added raii-copyctr.t that tests direct initialization and copy construction of managed structs. --- tests/raii-copyctr.t | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/raii-copyctr.t diff --git a/tests/raii-copyctr.t b/tests/raii-copyctr.t new file mode 100644 index 000000000..01657be95 --- /dev/null +++ b/tests/raii-copyctr.t @@ -0,0 +1,57 @@ +require "terralibext" --load 'terralibext' to enable raii +--[[ + 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.methods.__copy' then the copy + should be performed using this method. +--]] + +local test = require("test") +io = terralib.includec("stdio.h") + +struct A{ + data : int +} + +A.methods.__init = terra(self : &A) + io.printf("__init: calling initializer.\n") + self.data = 1 +end + +A.methods.__dtor = terra(self : &A) + io.printf("__dtor: calling destructor.\n") + self.data = -1 +end + +A.methods.__copy = terra(from : &A, to : &A) + io.printf("__copy: calling copy assignment {&A, &A} -> {}.\n") + to.data = to.data + from.data + 1 +end + +terra test1() + var a : A --__init -> a.data = 1 + var aa = a --__init + __copy -> a.data = 3 + return aa.data +end + +-- aa.data = aa.data + a.data + 1 = 3 +test.eq(test1(), 3) + +--since A is managed, an __init, __dtor, and __copy will +--be generated +struct B{ + data : A +} + +terra test2() + var a : A --__init -> a.data = 1 + var b = B{a} --__init + __copy -> b.data.data = 3 + return b.data.data +end + +-- b.data.data = b.data.data + a.data + 1 = 3 +test.eq(test2(), 3) \ No newline at end of file From 779c01f1cb67526d728fab5468295262ca34226b Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 25 Nov 2024 16:50:35 +0100 Subject: [PATCH 75/81] added tests for returning managed vars from function --- tests/raii.t | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/raii.t b/tests/raii.t index 96542c6ae..be0b1d8b7 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -93,4 +93,29 @@ terra testcopyassignment2() x = a return x end -test.eq(testcopyassignment2(), 5) \ No newline at end of file +test.eq(testcopyassignment2(), 5) + +printtestheader("raii.t - return from function.") +terra returnone() + var a = A{4} + return a +end + +terra testreturnfromfun1() + var a = returnone() + return a.data +end +test.eq(testreturnfromfun1(), 4) + +printtestheader("raii.t - return tuple from function.") +terra returntwo() + var a = A{4} + var b = A{5} + return a, b +end + +terra testreturnfromfun2() + var a, b = returntwo() + return a.data * b.data +end +test.eq(testreturnfromfun2(), 20) \ No newline at end of file From 952b904c1531d1c13ce61ee0a336d7779ccdfb36 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 25 Nov 2024 20:19:31 +0100 Subject: [PATCH 76/81] testing return managed variables from functions. --- tests/raii.t | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/raii.t b/tests/raii.t index be0b1d8b7..8f6fed6a3 100644 --- a/tests/raii.t +++ b/tests/raii.t @@ -95,9 +95,12 @@ terra testcopyassignment2() end test.eq(testcopyassignment2(), 5) +--generate implementation for __move +terralib.ext.addmissing.__move(A) + printtestheader("raii.t - return from function.") terra returnone() - var a = A{4} + var a = A{4}:__move() --call __move to move resources into 'a' (make sure copy constructor is not called) return a end @@ -108,9 +111,10 @@ end test.eq(testreturnfromfun1(), 4) printtestheader("raii.t - return tuple from function.") + terra returntwo() - var a = A{4} - var b = A{5} + var a = A{4}:__move() --call __move to move resources into 'a' (make sure copy constructor is not called) + var b = A{5}:__move() --same for 'b' return a, b end @@ -118,4 +122,5 @@ terra testreturnfromfun2() var a, b = returntwo() return a.data * b.data end +print(testreturnfromfun2()) test.eq(testreturnfromfun2(), 20) \ No newline at end of file From e75ff3d69e66596622257beb79ac89cbb2db8947 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 25 Nov 2024 20:20:29 +0100 Subject: [PATCH 77/81] structcast now working correctly, also when returning multiple args from functions. --- src/terralib.lua | 242 +++++++++++++++++++++++++++-------------------- 1 file changed, 138 insertions(+), 104 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index ddab47436..c6091a007 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3383,117 +3383,151 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) return regular, byfcall end - local function createassignment(anchor,lhs,rhs) + --struct assignment pattern matching applies? true / false + local function patterncanbematched(lhs, rhs) + local last = rhs[#rhs] + if last.type:isstruct() and last.type.convertible == "tuple" and #last.type.entries + #rhs - 1 == #lhs then + return true + end + return false + end + + local createassignment, createregularassignment + + --try unpack struct and perform pattern match + local function trystructpatternmatching(anchor, lhs, rhs) + local last = rhs[#rhs] + local av,v = allocvar(anchor,last.type,"") + local newlhs,lhsp,rhsp = terralib.newlist(),terralib.newlist(),terralib.newlist() + for i,l in ipairs(lhs) do + if i < #rhs then + newlhs:insert(l) + else + lhsp:insert(l) + rhsp:insert((insertselect(v,"_"..tostring(i - #rhs)))) + end + end + newlhs[#rhs] = av + local a1 = createassignment(anchor, newlhs, rhs) --potential managed assignment + local a2 = createregularassignment(anchor, lhsp, rhsp) --regular assignment - __copy and __dtor are possibly already called in 'a1' + return createstatementlist(anchor, List {a1, a2}) + end + + --create regular assignment - no managed types + function createregularassignment(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 - --struct pattern match - local av,v = allocvar(anchor,last.type,"") - local newlhs,lhsp,rhsp = terralib.newlist(),terralib.newlist(),terralib.newlist() - for i,l in ipairs(lhs) do - if i < #rhs then - newlhs:insert(l) - else - lhsp:insert(l) - rhsp:insert((insertselect(v,"_"..tostring(i - #rhs)))) - end - end - newlhs[#rhs] = av - local a1,a2 = createassignment(anchor,newlhs,rhs), createassignment(anchor,lhsp,rhsp) - return createstatementlist(anchor, List {a1, a2}) + if patterncanbematched(lhs, rhs) then + return trystructpatternmatching(anchor, lhs, rhs) + end + end + --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) + end - 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) - 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 + local function createmanagedassignment(anchor, lhs, rhs) + --special case where a rhs struct is unpacked + if #lhs > #rhs and #rhs > 0 then + if patterncanbematched(lhs, rhs) then + return trystructpatternmatching(anchor, lhs, rhs) end - return newobject(anchor,T.assignment,lhs,rhs) - 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) - 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) - --if 'v' is a managed variable then - --(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 - --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 + --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) + 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) + --if 'v' is a managed variable then + --(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 + --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 - --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 - 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 - if not v.type then - v:settype(rhstype) - end - 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) - --apply copy assignment - memory resource management is in the - --hands of the programmer - stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + end + --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 + 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 + if not v.type then + v:settype(rhstype) end - end - if #stmts==0 then - --standard case, no meta-copy-assignments - return newobject(anchor,T.assignment, regular.lhs, regular.rhs) - else - --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)) + stmts:insert(v) + local init = checkmetainit(anchor, v) + if init then + stmts:insert(init) end - stmts:insertall(post) - return createstatementlist(anchor, stmts) + stmts:insert(checkmetacopyassignment(anchor, byfcall.rhs[i], v)) + else + ensurelvalue(v) + --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 + --standard case, no meta-copy-assignments + return newobject(anchor,T.assignment, regular.lhs, regular.rhs) + else + --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 + stmts:insertall(post) + return createstatementlist(anchor, stmts) + end + end + + --create assignment - regular / copy assignment + function createassignment(anchor, lhs, rhs) + if not terralib.ext or #lhs < #rhs then + --regular assignment - __init, __copy and __dtor will not be scheduled + return createregularassignment(anchor, lhs, rhs) + else + --managed assignment - __init, __copy and __dtor are scheduled for managed + --variables + return createmanagedassignment(anchor, lhs, rhs) end end @@ -3788,11 +3822,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 3f9d92f33b44c6f27975e32bec07be041b0005b0 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 25 Nov 2024 21:22:28 +0100 Subject: [PATCH 78/81] forgot to uncomment macos specific code. --- src/terralib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/terralib.lua b/src/terralib.lua index c6091a007..98ca2c8e2 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3822,11 +3822,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 b65e647574e534d6377dbb6c97c84ca52da385d8 Mon Sep 17 00:00:00 2001 From: rrhiemstar Date: Mon, 25 Nov 2024 21:34:30 +0100 Subject: [PATCH 79/81] fixed typo 'environment' in README; added some test arctifacts and .dea and cmake-build-debug to .ignore list. --- .gitignore | 8 ++++++++ release/share/terra/README.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d3d42d9f3..de8902a11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/.idea +/cmake-build-debug /build /docs/_site /docs/_vendor @@ -18,6 +20,12 @@ /tests/dynlib /tests/dynlib.exe /tests/not_bc +/tests/class2 +/tests/inline_c.exe +/tests/objc +/tests/objc2 +/tests/stdio.exe + *.bc *.ll diff --git a/release/share/terra/README.md b/release/share/terra/README.md index 69269d1ca..56337c8d5 100644 --- a/release/share/terra/README.md +++ b/release/share/terra/README.md @@ -27,7 +27,7 @@ Installing Terra Terra currently runs on Linux (x86_64, AArch64, PPC64le), macOS (x86_64, AArch64), FreeBSD (x86_64), and Windows (x86_64). Binary releases for popular versions of these systems are available [online](https://github.com/terralang/terra/releases), and we recommend you use them if possible because building Terra requires a working install of LLVM and Clang, which can be difficult to get working. -Note that as of macOS 10.15 Catalina, in order to include C headers in Terra you will need to define the following evironment variable: +Note that as of macOS 10.15 Catalina, in order to include C headers in Terra you will need to define the following environment variable: export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" From 440965d698ea7015e82c12eb6bcf258cf99300a8 Mon Sep 17 00:00:00 2001 From: renehiemstra Date: Wed, 27 Nov 2024 06:13:12 +0100 Subject: [PATCH 80/81] removed ducplicate 'cmake-build-debug' from .ignore file. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 83f293c9b..7edf44f3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /.idea /cmake-build-debug /build -/cmake-build-debug /docs/_site /docs/_vendor /install From 14234ca68ea111303d5dc853717847f2a252f5f1 Mon Sep 17 00:00:00 2001 From: renehiemstra Date: Wed, 27 Nov 2024 11:04:29 +0100 Subject: [PATCH 81/81] added raii.md to documentation folder. updated content. --- docs/raii.md | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/raii.md | 152 ------------------------------------------- 2 files changed, 177 insertions(+), 152 deletions(-) create mode 100644 docs/raii.md delete mode 100644 lib/raii.md diff --git a/docs/raii.md b/docs/raii.md new file mode 100644 index 000000000..39d588f29 --- /dev/null +++ b/docs/raii.md @@ -0,0 +1,177 @@ +# Deterministic Automated Resource Management + +Resource management in programming languages generally falls into one of the following categories: + +1. **Manual allocation and deallocation** +2. **Automatic garbage collection** +3. **Automatic scope-bound resource management** (commonly referred to as [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization), or *Resource Acquisition Is Initialization*). + +Traditionally, Terra has only supported manual, C-style resource management. While functional, this approach limits the full potential of Terra’s powerful metaprogramming capabilities. To address this limitation, the current implementation introduces **automated resource management**. + +--- + +## Scope-Bound Resource Management (RAII) + +The new implementation provides **scope-bound resource management (RAII)**, a method typically associated with systems programming languages like C++ and Rust. With RAII, a resource's lifecycle is tied to the stack object that manages it. When the object goes out of scope and is not explicitly returned, the associated resource is automatically destructed. + +### Examples of Resources Managed via RAII: +- Allocated heap memory +- Threads of execution +- Open sockets +- Open files +- Locked mutexes +- Disk space +- Database connections + +--- + +## Experimental Implementation Overview + +The current Terra implementation supports the **Big Three** (as described by the [Rule of Three](https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) in C++): + +1. **Object destruction** +2. **Copy assignment** +3. **Copy construction** + +However, **rvalue references** (introduced in C++11) are not supported in Terra. As a result, the current RAII implementation is comparable to that of **C++03** or **Rust**. + +### Key Features: +Compiler support is provided for the following methods: +```terra +A.methods.__init(self : &A) +A.methods.__dtor(self : &A) +(A or B).methods.__copy(from : &A, to : &B) +``` +These methods facilitate the implementation of smart containers and pointers, such as `std::string`, `std::vector` and `std::unique_ptr`, `std::shared_ptr`, `boost:offset_ptr` in C++. + +### Design Overview +* No Breaking Changes: This implementation does not introduce breaking changes to existing Terra code. No new keywords are required, ensuring that existing syntax remains compatible. +* Type Checking Integration: These methods are introduced during the type-checking phase (handled in terralib.lua). They can be implemented as either macros or Terra functions. +* Composability: The implementation follows simple, consistent rules that ensure smooth compatibility with existing Terra syntax for construction, casting, and function returns. +* Heap resources: Heap resources are allocated and deallocated using standard C functions like malloc and free, leaving memory allocation in the hands of the programmer. The idea here is that remaining functionality, such as allocators, are implemented as libraries. + +--- + +## Safety and Future Work + +While safety is a growing concern in programming, the current implementation has several safety challenges, similar to those in C++ or Rust's unsafe mode. + +### Future Work Includes: +1. **Library support for composable allocators**: + - Tracing or debugging allocators to detect memory leaks or other faulty behavior. +2. **Compile-time borrow checking**: + - Similar to Rust or Mojo, ensuring safer resource usage. +3. **Improved lifetime analysis**: + - Making the compiler aware of when resources (e.g., heap allocations) are introduced. + - Making the compiler aware of when resources are last used. + +--- + +## 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 we assume `struct A` is a managed type. + +To enable RAII, import the library */lib/terralibext.t* using +```terra +require "terralibext" +``` +The compiler only checks for `__init`, `__dtor` and `__copy` in case this library is loaded. + +### 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() --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. +``` +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 a (overloaded) terra function or a macro. + +The programmer is responsible 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 : B = a +``` +is replaced by the following statements +``` +var b : 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. + +### 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*. + +## 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.t* tests `__copy`. +* *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 + ``` \ No newline at end of file diff --git a/lib/raii.md b/lib/raii.md deleted file mode 100644 index 5a7771511..000000000 --- a/lib/raii.md +++ /dev/null @@ -1,152 +0,0 @@ -# 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 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: -``` - 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() --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. -``` - 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 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 : B = a -``` -is replaced by the following statements -``` - var b : 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. - -### 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*. - -## 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 -``` -* 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 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 signals a heap allocation. This way the compiler is made aware of all heap allocations being made. \ No newline at end of file