| chigraph master Systems programming language written for beginners in LLVM

FunctionValidator.cpp
Go to the documentation of this file.
1
3 #include "chi/FunctionValidator.hpp"
4 #include "chi/DataType.hpp"
5 #include "chi/GraphFunction.hpp"
6 #include "chi/GraphModule.hpp"
7 #include "chi/NodeInstance.hpp"
8 #include "chi/NodeType.hpp"
9 #include "chi/Support/Result.hpp"
10
11 #include <unordered_map>
12
13 namespace chi {
14
15 Result validateFunction(const GraphFunction& func) {
16 Result res;
17
18 res += validateFunctionConnectionsAreTwoWay(func);
19 res += validateFunctionNodeInputs(func);
20 res += validateFunctionExecOutputs(func);
21 res += validateFunctionEntryType(func);
22 res += validateFunctionExitTypes(func);
23
24 if (func.name() == "main" && func.module().shortName() == "main") {
25 res += validateFunctionMainSignature(func);
26 }
27
28 return res;
29 }
30
31 Result validateFunctionConnectionsAreTwoWay(const GraphFunction& func) {
32 Result res;
33
34 // make sure they all get the context
35 auto funcCtx =
36 res.addScopedContext({{"function", func.name()}, {"module", func.module().fullName()}});
37
38 // make sure all connections connect back
39 for (const auto& node : func.nodes()) {
40 // go through input data
41 auto id = 0ull;
42 for (const auto& conn : node.second->inputDataConnections) {
43 if (conn.first == nullptr) {
44 res.addEntry("EUKN", "Node is missing an input data connection",
45 {{"Node ID", node.second->stringId()},
46 {"nodetype", node.second->type().qualifiedName()},
47 {"requested id", id}});
48 ++id;
49 continue;
50 }
51
52 // make sure it connects back
53 bool connectsBack = false;
54 for (const auto& remoteConn : conn.first->outputDataConnections[conn.second]) {
55 if (remoteConn.first == node.second.get() && remoteConn.second == id) {
56 connectsBack = true;
57 break;
58 }
59 }
60 if (!connectsBack) {
61 res.addEntry("EUKN", "Data connection doesn't connect back",
62 {{"Left Node", conn.first->stringId()},
63 {"Right Node", node.second->stringId()},
64 {"Right input ID", id}});
65 }
66 ++id;
67 }
68
69 // output data
70 id = 0ull;
71 for (const auto& outputDataSlot : node.second->outputDataConnections) {
72 // this connection type can make multiple connections, so two for loops are needed
73 for (const auto& connection : outputDataSlot) {
74 assert(connection.first != nullptr);
75
76 if (connection.first->inputDataConnections.size() <= connection.second) {
77 res.addEntry("EUKN", "Input data port not found in node",
78 {{"Node ID", connection.first->stringId()},
79 {"requested id", connection.second}});
80 continue;
81 }
82
83 auto& remoteConn = connection.first->inputDataConnections[connection.second];
84 if (remoteConn.first != node.second.get() || remoteConn.second != id) {
85 res.addEntry("EUKN", "Data connection doesn't connect back",
86 {{"Left Node", node.second->stringId()},
87 {"Right Node", connection.first->stringId()},
88 {"Right input ID", connection.second}});
89 }
90 }
91 ++id;
92 }
93
94 // input exec
95 id = 0ull;
96 for (const auto& inputExecSlot : node.second->inputExecConnections) {
97 // this connection type can make multiple connections, so two for loops are needed
98 for (const auto& connection : inputExecSlot) {
99 auto& remoteConn = connection.first->outputExecConnections[connection.second];
100 if (remoteConn.first != node.second.get() || remoteConn.second != id) {
101 res.addEntry("EUKN", "Exec connection doesn't connect back",
102 {{"Left Node", connection.first->stringId()},
103 {"Left Node Type", connection.first->type().qualifiedName()},
104 {"Right Node", node.second->stringId()},
105 {"Right Node Type", node.second->type().qualifiedName()},
106 {"Left output ID", connection.second}});
107 }
108 }
109 ++id;
110 }
111
112 // output exec
113 id = 0ull;
114 for (const auto& connection : node.second->outputExecConnections) {
115 bool connectsBack = false;
116
117 if (connection.first == nullptr) {
118 ++id;
119 continue;
120 }
121 for (const auto& remoteConnection :
122 connection.first->inputExecConnections[connection.second]) {
123 if (remoteConnection.second == id && remoteConnection.first == node.second.get()) {
124 connectsBack = true;
125 break;
126 }
127 }
128
129 if (!connectsBack) {
130 res.addEntry("EUKN", "Exec connection doesn't connect back",
131 {{"Left Node", node.second->stringId()},
132 {"Left Node Type", node.second->type().qualifiedName()},
133 {"Right Node", connection.first->stringId()},
134 {"Right Node Type", connection.first->type().qualifiedName()},
135 {"Left output ID", id}});
136 }
137
138 ++id;
139 }
140 }
141
142 // make sure all entires have the right type
143
144 return res;
145 }
146
147 namespace {
148
152 Result validatePath(
153 const NodeInstance& inst, int inExecId,
154 std::unordered_map<const NodeInstance*, std::vector<int> /*in Exec id*/> alreadyCalled) {
155 Result res;
156
157 // if we've already been here, then return, it's a loop
158 {
159 auto iter = alreadyCalled.find(&inst);
160 if (iter != alreadyCalled.end() &&
161 std::find(iter->second.begin(), iter->second.end(), inExecId) != iter->second.end()) {
162 return res;
163 }
164 }
165
166 // make sure the inputs are already in processed
167 auto id = 0ull;
168 for (const auto& conn : inst.inputDataConnections) {
169 if (conn.first == nullptr) {
170 res.addEntry("EUKN", "Node is missing an input data connection",
171 {{"Node ID", inst.stringId()},
172 {"dataid", id},
173 {"nodetype", inst.type().qualifiedName()}});
174 ++id;
175 continue;
176 }
177
178 if (!conn.first->type().pure() && alreadyCalled.find(conn.first) == alreadyCalled.end()) {
179 res.addEntry("EUKN", "Node that accepts data from another node is called first",
180 {{"Node ID", inst.stringId()}, {"othernodeid", conn.first->stringId()}});
181 }
182
183 ++id;
184 }
185
186 alreadyCalled[&inst].push_back(inExecId);
187
188 // call this on the nodes this calls
189 for (const auto& conn : inst.outputExecConnections) {
190 if (conn.first == nullptr) { continue; }
191 res += validatePath(*conn.first, conn.second, alreadyCalled);
192 }
193
194 return res;
195 }
196 } // anonymous namespace
197
198 Result validateFunctionNodeInputs(const GraphFunction& func) {
199 Result res;
200
201 // make sure they all get the context
202 auto funcCtx =
203 res.addScopedContext({{"function", func.name()}, {"module", func.module().fullName()}});
204
205 auto entry = func.entryNode();
206
207 if (entry == nullptr) { return res; }
208
209 std::unordered_map<const NodeInstance*, std::vector<int>> alreadyCalled;
210 alreadyCalled.emplace(entry, std::vector<int>{});
211
212 // no need to create a processed because you can't create a loop with entry in it
213
214 for (const auto& conn : entry->outputExecConnections) {
215 if (conn.first == nullptr) { continue; }
216 res += validatePath(*conn.first, conn.second, alreadyCalled);
217 }
218
219 return res;
220 }
221
222 Result validateFunctionExecOutputs(const GraphFunction& func) {
223 // make sure all exec outputs exist, and raise an error otherwise.
224 // TODO (#70): quickfix to add return
225
226 Result res;
227
228 // make sure they all get the context
229 auto funcCtx =
230 res.addScopedContext({{"function", func.name()}, {"module", func.module().fullName()}});
231
232 for (const auto& nodepair : func.nodes()) {
233 auto node = nodepair.second.get();
234
235 auto id = 0ull;
236 for (const auto& conn : node->outputExecConnections) {
237 if (conn.second == ~0ull || conn.first == nullptr) {
238 res.addEntry("EUKN", "Node is missing an output exec connection",
239 {{"Node ID", node->stringId()}, {"Missing ID", id}});
240 }
241
242 ++id;
243 }
244 }
245
246 return res;
247 }
248
249 Result validateFunctionEntryType(const GraphFunction& func) {
250 Result res;
251
252 auto entry = func.entryNode();
253 if (!entry) {
254 res.addEntry("EUKN", "Function must have a valid entry node to validate the entry type",
255 {{"Function", func.name()}, {"Module", func.module().fullName()}});
256 return res;
257 }
258
259 // make sure that the entry node has the right data types
260 if (!std::equal(func.dataInputs().begin(), func.dataInputs().end(),
261 entry->type().dataOutputs().begin())) {
262 nlohmann::json inFunc = nlohmann::json::array();
263 for (auto& in : func.dataInputs()) {
264 inFunc.push_back({{in.name, in.type.qualifiedName()}});
265 }
266
267 nlohmann::json inEntry = nlohmann::json::array();
268 for (auto& in :
269 entry->type().dataOutputs()) { // outputs to entry are inputs to the function
270 inEntry.push_back({{in.name, in.type.qualifiedName()}});
271 }
272
273 res.addEntry("EUKN", "Inputs to function doesn't match function inputs",
274 {{"Function Inputs", inFunc}, {"Entry Inputs", inEntry}});
275 return res;
276 }
277
278 return res;
279 }
280
281 Result validateFunctionExitTypes(const GraphFunction& func) {
282 Result res;
283
284 // make sure that each exit node has the right data types
285 for (auto exitNode : func.nodesWithType("lang", "exit")) {
286 if (!std::equal(func.dataOutputs().begin(), func.dataOutputs().end(),
287 exitNode->type().dataInputs().begin())) {
288 nlohmann::json outFunc = nlohmann::json::array();
289 for (auto& out : func.dataOutputs()) {
290 outFunc.push_back({{out.name, out.type.qualifiedName()}});
291 }
292
293 nlohmann::json outExit = nlohmann::json::array();
294 for (auto& out : exitNode->type().dataOutputs()) {
295 // inputs to the exit are outputs to the function
296 outExit.push_back({{out.name, out.type.qualifiedName()}});
297 }
298
299 res.addEntry("EUKN", "Outputs to function doesn't match function exit",
300 {{"Function Outputs", outFunc},
301 {"Exit Outputs", outExit},
302 {"Node ID", exitNode->stringId()}});
303 return res;
304 }
305 }
306
307 return res;
308 }
309
310 Result validateFunctionMainSignature(const GraphFunction& func) {
311 Result res;
312
313 if (func.execInputs().size() != 1) {
314 res.addEntry("EUKN", "A main function must have exactly one exec in",
315 {{"Exec Inputs", func.execInputs()}});
316 }
317 if (func.execOutputs().size() != 1) {
318 res.addEntry("EUKN", "A main function must have exactly one exec out",
319 {{"Exec Outputs", func.execOutputs()}});
320 }
321
322 if (!func.dataInputs().empty()) {
323 auto dataJson = nlohmann::json::array();
324 for (const auto& param : func.dataInputs()) {
325 dataJson.push_back({{param.name, param.type.qualifiedName()}});
326 }
327
328 res.addEntry("EUKN", "A main function must have no data inputs",
329 {{"Data Inputs", dataJson}});
330 }
331 if (func.dataOutputs().size() != 1 ||
332 func.dataOutputs()[0].type.qualifiedName() != "lang:i32") {
333 auto dataJson = nlohmann::json::array();
334 for (const auto& param : func.dataOutputs()) {
335 dataJson.push_back({{param.name, param.type.qualifiedName()}});
336 }
337
338 res.addEntry("EUKN", "A main function must have exactly one data output that's a lang:i32",
339 {{"Data Outputs", dataJson}});
340 }
341 return res;
342 }
343
344 } // namespace chi
chi::GraphFunction::dataOutputs
const std::vector< NamedDataType > & dataOutputs() const
Get the function data outputs in the format {type, docstring}.
Definition: GraphFunction.hpp:180
FunctionValidator.hpp
Defines functions for validating GraphFunction objects.
chi::GraphFunction
this is an AST-like representation of a function in a graph It is used for IDE-like behavior...
Definition: GraphFunction.hpp:23
chi::Result::addEntry
void addEntry(const char *ec, const char *overview, nlohmann::json data)
Add a entry to the result, either a warning or an error.
Definition: Result.cpp:52
chi::validateFunctionExecOutputs
Result validateFunctionExecOutputs(const GraphFunction &func)
Make sure nodes have an output connection.
Definition: FunctionValidator.cpp:222
chi::GraphFunction::execInputs
const std::vector< std::string > & execInputs() const
Get the function exec inputs.
Definition: GraphFunction.hpp:216
chi::NodeInstance::stringId
std::string stringId() const
Get the ID as a string.
Definition: NodeInstance.hpp:68
chi::GraphFunction::execOutputs
const std::vector< std::string > & execOutputs() const
Get the function exec outputs.
Definition: GraphFunction.hpp:241
chi::validateFunctionEntryType
Result validateFunctionEntryType(const GraphFunction &func)
Make sure the function entry type aligns with the function type.
Definition: FunctionValidator.cpp:249
Result.hpp
Defines the Result class and related functions.
chi::validateFunctionConnectionsAreTwoWay
Result validateFunctionConnectionsAreTwoWay(const GraphFunction &func)
Make sure that connections connect back and that they have the same types.
Definition: FunctionValidator.cpp:31
chi::validateFunction
Result validateFunction(const GraphFunction &func)
Validate that a function is compilable.
Definition: FunctionValidator.cpp:15
chi::NodeInstance::type
NodeType & type()
Get the type of the instance.
Definition: NodeInstance.hpp:46
chi::NodeInstance
An instance of a node.
Definition: NodeInstance.hpp:19
chi::NodeInstance::inputDataConnections
std::vector< std::pair< NodeInstance *, size_t > > inputDataConnections
The connections that go into this node, data.
Definition: NodeInstance.hpp:77
chi::GraphFunction::module
GraphModule & module() const
Get the GraphModule that contains this GraphFunction.
Definition: GraphFunction.hpp:328
chi::GraphFunction::name
std::string name() const
Get the name of the function.
Definition: GraphFunction.hpp:313
chi::validateFunctionNodeInputs
Result validateFunctionNodeInputs(const GraphFunction &func)
Make sure that nodes are called before their outputs are used.
Definition: FunctionValidator.cpp:198
chi::ChiModule::fullName
std::string fullName() const
Get the full name of the module.
Definition: ChiModule.hpp:61
chi::Result::addScopedContext
ScopedContext addScopedContext(const nlohmann::json &data)
Add a context with a scope Example usage: chi::Result res; res.contextJson(); // returns {} { aut...
Definition: Result.hpp:123
chi::GraphFunction::entryNode
NodeInstance * entryNode() const noexcept
Gets the node with type lang:entry returns nullptr on failure Also returns nullptr if there are two e...
Definition: GraphFunction.cpp:44
chi::GraphFunction::nodes
std::unordered_map< boost::uuids::uuid, std::unique_ptr< NodeInstance > > & nodes()
Get the nodes in the function Usually called by connectData or connectExec or GraphFunction.
Definition: GraphFunction.hpp:51
NodeType.hpp
Defines the NodeType class.
chi::validateFunctionMainSignature
Result validateFunctionMainSignature(const GraphFunction &func)
Make sure it&#39;s a valid signautre for a main function.
Definition: FunctionValidator.cpp:310
chi::validateFunctionExitTypes
Result validateFunctionExitTypes(const GraphFunction &func)
Make sure the function exit types align with the function type.
Definition: FunctionValidator.cpp:281
DataType.hpp
Defines the DataType class.
chi::GraphFunction::nodesWithType
std::vector< NodeInstance * > nodesWithType(const boost::filesystem::path &module, boost::string_view name) const noexcept
Gets the nodes with a given type.
Definition: GraphFunction.cpp:87
chi::NodeInstance::outputExecConnections
std::vector< std::pair< NodeInstance *, size_t > > outputExecConnections
The connections that go out of this node, exec.
Definition: NodeInstance.hpp:80
GraphModule.hpp
Defines the GraphModule class.
chi
The namespace where chigraph lives.
Definition: BitcodeParser.hpp:12
chi::NodeType::qualifiedName
std::string qualifiedName() const
Get the qualified name of the node type, like module.name():name()
Definition: NodeType.cpp:17
chi::GraphFunction::dataInputs
const std::vector< NamedDataType > & dataInputs() const
Get the function data inputs in the format {type, docstring}.
Definition: GraphFunction.hpp:145
chi::ChiModule::shortName
std::string shortName() const
Get the short name of the module (the last bit)
Definition: ChiModule.hpp:58
chi::Result
The result object, used for identifiying errors with good diagnostics.
Definition: Result.hpp:72
GraphFunction.hpp
Declares the GraphFunction class.
NodeInstance.hpp
Defines the NodeInstance class and related functions.