-
Notifications
You must be signed in to change notification settings - Fork 0
/
vtemplate.t
157 lines (143 loc) · 5.09 KB
/
vtemplate.t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
local templatize = require("templatize")
local inheritance = require("inheritance")
local Vector = require("vector")
local util = require("util")
local data = {}
local vmethods = {} -- So function pointers don't get gc'ed
local function getDataForClass(class)
for c,d in pairs(data) do
if inheritance.issubclass(class, c) then
return d
end
end
local d = {}
data[class] = d
return d
end
-- Create a stub method to be put in a concrete vtable
-- When the stub is called, it will replace itself with the actual virtual method
-- that's being requested (via JIT)
local function addStub(concreteDatum)
-- We're adding to the end of the vtable, so the vtable index is the end
-- of the vector.
local vtableindex = concreteDatum.vtable:getpointer().size
local abstractDatum = concreteDatum.abstractDatum
local params = abstractDatum.id2params[vtableindex+1]
local typ = abstractDatum:specialzedFnType(unpack(params))
local syms = {}
for _,t in ipairs(typ.type.parameters) do table.insert(syms, symbol(t)) end
local terra stub([syms])
-- Replace this stub with the actual implementation
[concreteDatum.compileAndReplace](vtableindex)
-- Re-invoke the virtual template function, which will call the newly
-- compiled implementation instead of the stub.
return [abstractDatum.templatefn(unpack(params))]([syms])
end
-- Add the stub to the vtable
table.insert(vmethods, stub)
Vector(&opaque).methods.push(concreteDatum.vtable:getpointer(), stub:getpointer())
end
local datumMT =
{
vtableName = function(self) return string.format("%sVtable", self.name) end,
specialzedFnType = function(self, ...)
local typ = self.typfn(...)
local newparams = util.copytable(typ.type.parameters)
local rettype = typ.type.returntype
table.insert(newparams, 1, &self.class)
return terralib.types.funcpointer(newparams, rettype)
end
}
datumMT.__index = datumMT
local function newFunction(class, name, typfn, fn)
local nextid = 1
local datum =
{
class = class,
name =name,
typfn = typfn,
-- Unique ID for every parameter set
params2id = templatize(function(...)
nextid = nextid + 1
return nextid - 1
end),
-- Map from ID back to parameters
id2params = {},
-- Concrete implementations of this function
concretes = {}
}
setmetatable(datum, datumMT)
-- Call function on parameter set, returns a "specialized virtual function"
-- (Actually a macro that invokes the right vtable entry)
datum.templatefn = templatize(function(...)
local typ = datum:specialzedFnType(...)
local id = datum.params2id(...)
local vtableindex = id-1
-- If this is a new param setting (i.e. the id is bigger than the length
-- of our id->params map), then make a new stub for all registered
-- concrete implementations of this vtemplate
if id > #datum.id2params then
datum.id2params[id] = {...}
for _,concreteDatum in ipairs(datum.concretes) do
addStub(concreteDatum)
end
end
return macro(function(inst, ...)
local fnptr = `[typ]([inst].[datum:vtableName()]:get(vtableindex))
local args = {...}
table.insert(args, 1, inst)
return `fnptr([args])
end)
end)
-- vtable pointer (every instance of class has one)
class.entries:insert({field = datum:vtableName(), type = &Vector(&opaque)})
return datum
end
local function setupConcreteImplementation(concreteClass, concreteFn, datum)
local concreteDatum =
{
abstractDatum = datum,
class = concreteClass,
fn = concreteFn,
vtable = global(Vector(&opaque))
}
Vector(&opaque).methods.__construct(concreteDatum.vtable:getpointer())
-- When a stub is called, compile the actual implementation and put
-- in the vtable where the stub used to be.
concreteDatum.compileAndReplace = function(vtableindex)
local params = datum.id2params[vtableindex+1]
local specfn = concreteFn(unpack(params))
table.insert(vmethods, specfn)
Vector(&opaque).methods.set(concreteDatum.vtable:getpointer(), vtableindex, specfn:getpointer())
end
-- Create stubs for all the parameter sets we've seen so far.
for i,params in ipairs(datum.id2params) do
addStub(concreteDatum)
end
-- Add a vtable initializer method to the class
-- (Or augment an existing vtable initializer)
local oldinit = concreteClass.methods.__initvtable
concreteClass.methods.__initvtable = terra(self: &concreteClass)
[oldinit and (quote oldinit(self) end) or (quote end)]
self.[datum:vtableName()] = &[concreteDatum.vtable]
end
-- Finally, register this new concrete implementation with the abstract datum.
-- This is critical: If/when new parameter sets are encountered, new stubs can be added
-- to this concrete class's vtable.
table.insert(datum.concretes, concreteDatum)
end
-- typfn is a function from template parameters to a function type
-- fn only needs to be provided for concrete derived classes
local function virtualTemplate(class, name, typfn, fn)
local classDatum = getDataForClass(class)
local nameDatum = classDatum[name]
if not nameDatum then
nameDatum = newFunction(class, name, typfn, fn)
classDatum[name] = nameDatum
end
if fn then
setupConcreteImplementation(class, fn, nameDatum)
end
return nameDatum.templatefn
end
return virtualTemplate