Skip to content

Commit 6a237f7

Browse files
committed
commented python serialization
1 parent f80a9c8 commit 6a237f7

File tree

3 files changed

+195
-80
lines changed

3 files changed

+195
-80
lines changed

lib/conversions/src/RLCToPython.cpp

Lines changed: 81 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -18,91 +18,15 @@ limitations under the License.
1818
#include "mlir/Transforms/DialectConversion.h"
1919
#include "rlc/dialect/ActionArgumentAnalysis.hpp"
2020
#include "rlc/dialect/Dialect.h"
21+
#include "rlc/dialect/MemberFunctionsTable.hpp"
2122
#include "rlc/dialect/Operations.hpp"
2223
#include "rlc/dialect/Passes.hpp"
2324
#include "rlc/dialect/Types.hpp"
2425
#include "rlc/dialect/Visits.hpp"
2526
#include "rlc/utils/PatternMatcher.hpp"
2627
namespace mlir::rlc
2728
{
28-
// a table that keeps track of which member functions belong to which type
29-
class MemberFunctionsTable
30-
{
31-
public:
32-
MemberFunctionsTable(mlir::ModuleOp mod)
33-
{
34-
for (auto op : mod.getOps<mlir::rlc::FunctionOp>())
35-
if (op.getIsMemberFunction() and not op.isInternal() and
36-
(op.getArgumentTypes()[0].isa<mlir::rlc::ClassType>() or
37-
op.getArgumentTypes()[0].isa<mlir::rlc::AlternativeType>()))
38-
{
39-
auto selfType = op.getArgumentTypes()[0];
40-
auto key = selfType.getAsOpaquePointer();
41-
if (isInitFunction(selfType, op))
42-
initFunction[key] = op;
43-
else if (isDropFunction(selfType, op))
44-
dropFunction[key] = op;
45-
else if (isAssignFunction(selfType, op))
46-
assignFunction[key] = op;
47-
else
48-
typeToMethods[key].insert(op);
49-
}
50-
}
51-
52-
bool isInitFunction(mlir::Type t, mlir::rlc::FunctionOp method)
53-
{
54-
return (
55-
method.getUnmangledName() == "init" and
56-
returnsVoid(method.getType()).succeeded() and
57-
method.getType().getNumInputs() == 1 and
58-
method.getType().getInput(0) == t);
59-
}
60-
61-
bool isTriviallyInitializable(mlir::Type t)
62-
{
63-
return initFunction.count(t.getAsOpaquePointer()) == 0;
64-
}
65-
66-
bool isDropFunction(mlir::Type t, mlir::rlc::FunctionOp method)
67-
{
68-
return (
69-
method.getUnmangledName() == "drop" and
70-
returnsVoid(method.getType()).succeeded() and
71-
method.getType().getNumInputs() == 1 and
72-
method.getType().getInput(0) == t);
73-
}
74-
75-
bool isTriviallyDestructible(mlir::Type t)
76-
{
77-
return dropFunction.count(t.getAsOpaquePointer()) == 0;
78-
}
79-
80-
bool isAssignFunction(mlir::Type t, mlir::rlc::FunctionOp method)
81-
{
82-
return (
83-
method.getUnmangledName() == "assign" and
84-
returnsVoid(method.getType()).succeeded() and
85-
method.getType().getNumInputs() == 2 and
86-
method.getType().getInput(0) == t and
87-
method.getType().getInput(1) == t);
88-
}
89-
90-
bool isTriviallyCopiable(mlir::Type t)
91-
{
92-
return assignFunction.count(t.getAsOpaquePointer()) == 0;
93-
}
94-
95-
llvm::DenseSet<mlir::rlc::FunctionOp> getMemberFunctionsOf(mlir::Type type)
96-
{
97-
return typeToMethods[type.getAsOpaquePointer()];
98-
}
99-
100-
private:
101-
std::map<const void*, llvm::DenseSet<mlir::rlc::FunctionOp>> typeToMethods;
102-
std::map<const void*, mlir::rlc::FunctionOp> initFunction;
103-
std::map<const void*, mlir::rlc::FunctionOp> dropFunction;
104-
std::map<const void*, mlir::rlc::FunctionOp> assignFunction;
105-
};
29+
// Emits python imports and bookinping global variables.
10630
static void printPrelude(StreamWriter& writer, bool isMac, bool isWindows)
10731
{
10832
writer.writenl("import ctypes");
@@ -129,6 +53,8 @@ namespace mlir::rlc
12953
writer.endLine();
13054
}
13155

56+
// returns true if `type` has a immediate mapping onto a ctypes
57+
// type.
13258
static bool builtinCType(mlir::Type type)
13359
{
13460
if (auto casted = mlir::dyn_cast<mlir::rlc::FrameType>(type))
@@ -141,6 +67,8 @@ namespace mlir::rlc
14167
mlir::isa<mlir::rlc::StringLiteralType>(type);
14268
}
14369

70+
// returns true if the require holds the real content
71+
// in the `.value` member
14472
static bool needsUnwrapping(mlir::FunctionType type)
14573
{
14674
if (type.getNumResults() == 0)
@@ -252,7 +180,7 @@ namespace mlir::rlc
252180
printCallArgs(type.getInputs(), argsInfo, w, resultType);
253181

254182
// for functions that return something emit
255-
// return result
183+
// return __result
256184
// and if they return a builtin ctype type, add .value to
257185
// extract to convert it to a python builtin type instead
258186
if (returnsVoid(type).failed())
@@ -280,6 +208,18 @@ namespace mlir::rlc
280208
w.indentOnce(1).writenl("return").endLine();
281209
}
282210

211+
// When we declare a python function we
212+
// * Emit the mangled function
213+
// (eg: def rl_ugly_mangled_name_r_bool(arg: ctypes.c_bool):)
214+
// that dispaches directly to the symbol in the binary
215+
//
216+
// * Emit the non mangled typehinting
217+
// @overload
218+
// def nice_name(arg: Bool):
219+
// return
220+
//
221+
// * stick that overload in the signatures global list so
222+
// other people can discover it dynamically if they need.
283223
static void declarePythonFunction(
284224
llvm::StringRef unmangledName,
285225
llvm::ArrayRef<llvm::StringRef> argsInfo,
@@ -297,7 +237,6 @@ namespace mlir::rlc
297237
auto mangledName =
298238
mlir::rlc::mangledName(unmangledName, isMemberFunction, type);
299239

300-
// mangled wrapper
301240
printMangledWrapper(
302241
unmangledName,
303242
mangledName,
@@ -320,6 +259,12 @@ namespace mlir::rlc
320259
w.writenl("]").endLine();
321260
}
322261

262+
// the three rlc special functions init, drop and assing must
263+
// be special cased to ensure they are always called, otherwise
264+
// the user could access invalid memory.
265+
//
266+
// In practice this means overriding python __init__, __del__ and
267+
// clone so that we can dispatch to the proper rlc methods.
323268
void emitSpecialFunctions(
324269
mlir::Type type, mlir::rlc::StreamWriter& w, MemberFunctionsTable& table)
325270
{
@@ -370,6 +315,8 @@ namespace mlir::rlc
370315
}
371316
}
372317

318+
// emits the ctypes __fields__ member that specifies
319+
// the layout of the class.
373320
void emitMembers(
374321
llvm::ArrayRef<mlir::Type> types,
375322
llvm::ArrayRef<llvm::StringRef> memberNames,
@@ -419,6 +366,16 @@ namespace mlir::rlc
419366
w.endLine();
420367
}
421368

369+
// Python has no innate overload, so we have to create runtime
370+
// dispatchers that look at the types of arguments and find the
371+
// right overload.
372+
//
373+
// In practice this just means doing
374+
// def f(*args):
375+
// if len(args) == overload_arg_count and isinstance(args[0], t1) and ...:
376+
// return overload(*args)
377+
// ...
378+
// raise TypeError()
422379
void emitOverloadDispatcher(
423380
llvm::StringRef name,
424381
llvm::ArrayRef<mlir::FunctionType> overloads,
@@ -544,6 +501,15 @@ namespace mlir::rlc
544501
}
545502
};
546503

504+
// ActionFunction end up generating:
505+
// * A class that rappresents the ActionFunction
506+
// * A free function to start the ActionFunction
507+
// * Optionally, the precondition function of the free function.
508+
// * the is_done member function.
509+
//
510+
// then, for each actions statement:
511+
// * emit the member function to trigger that action
512+
// * emit the precondition of that action member function.
547513
class ActionToPythonFunction
548514
{
549515
private:
@@ -740,6 +706,9 @@ namespace mlir::rlc
740706
registerCommonTypeConversion(ser);
741707
}
742708

709+
// emitting a action function is ugly because we need to collect
710+
// the frame type from the action function, and the arguments from
711+
// the action statements.
743712
static std::string emitActionFunction(
744713
mlir::rlc::ClassType frameType,
745714
llvm::StringRef actionName,
@@ -863,6 +832,38 @@ namespace mlir::rlc
863832

864833
#define GEN_PASS_DEF_PRINTPYTHONPASS
865834
#include "rlc/dialect/Passes.inc"
835+
/***
836+
*This pass emits a python module that describes the content of the rlc
837+
*module being compiled. It does the following:
838+
* 1 For each ClassType and AlternativeType in the RLC program it emits a
839+
* CType structure or union that with same members, and makes sure that
840+
* constructors, clone and destructor invoke the right method in the RLC
841+
* shared library.
842+
*
843+
*
844+
* 2 For each alias it emits the same alias
845+
*
846+
* 3 For each free function it emits a ctypes function declaration that wraps
847+
* that RLC function, annotated with the real signature of the function. The
848+
* emitted function has the same mangled name as the function in the
849+
* library.
850+
*
851+
* Then, it emits a dispatcher function that accepts a argument list and
852+
* invokes the correct overload according to the types of the variables the
853+
* python user has provided.
854+
*
855+
* Finally it emits typehinting annotations so that the user can see the
856+
* proper overloads available for a free function
857+
*
858+
* 4 For each member function it does the same thing that it did in step 3,
859+
* but instead of putting it into the global name space, the generated stuff
860+
* is placed inside the class that own the free function
861+
*
862+
* 5 For each ActionFunction it emits the ActionFunction as a ctypes class,
863+
* and inside that class it emits the ctypes function wrappers to invoke
864+
* the is_done function and actions statements that the ActionFunction
865+
* declares.
866+
***/
866867
struct PrintPythonPass: impl::PrintPythonPassBase<PrintPythonPass>
867868
{
868869
using impl::PrintPythonPassBase<PrintPythonPass>::PrintPythonPassBase;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
Copyright 2024 Massimo Fioravanti
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include "mlir/Pass/Pass.h"
18+
#include "mlir/Transforms/DialectConversion.h"
19+
#include "rlc/dialect/ActionArgumentAnalysis.hpp"
20+
#include "rlc/dialect/Dialect.h"
21+
#include "rlc/dialect/Operations.hpp"
22+
#include "rlc/dialect/Passes.hpp"
23+
#include "rlc/dialect/Types.hpp"
24+
#include "rlc/dialect/Visits.hpp"
25+
#include "rlc/utils/PatternMatcher.hpp"
26+
27+
namespace mlir::rlc
28+
{
29+
// a table that keeps track of which member functions belong to which type
30+
// You can query it to check if a type has a the init, drop and assign
31+
// methods, and to get all member functions of a type.
32+
class MemberFunctionsTable
33+
{
34+
public:
35+
MemberFunctionsTable(mlir::ModuleOp mod)
36+
{
37+
for (auto op : mod.getOps<mlir::rlc::FunctionOp>())
38+
if (op.getIsMemberFunction() and not op.isInternal() and
39+
(op.getArgumentTypes()[0].isa<mlir::rlc::ClassType>() or
40+
op.getArgumentTypes()[0].isa<mlir::rlc::AlternativeType>()))
41+
{
42+
auto selfType = op.getArgumentTypes()[0];
43+
auto key = selfType.getAsOpaquePointer();
44+
if (isInitFunction(selfType, op))
45+
initFunction[key] = op;
46+
else if (isDropFunction(selfType, op))
47+
dropFunction[key] = op;
48+
else if (isAssignFunction(selfType, op))
49+
assignFunction[key] = op;
50+
else
51+
typeToMethods[key].insert(op);
52+
}
53+
}
54+
55+
bool isInitFunction(mlir::Type t, mlir::rlc::FunctionOp method)
56+
{
57+
return (
58+
method.getUnmangledName() == "init" and
59+
returnsVoid(method.getType()).succeeded() and
60+
method.getType().getNumInputs() == 1 and
61+
method.getType().getInput(0) == t);
62+
}
63+
64+
bool isTriviallyInitializable(mlir::Type t)
65+
{
66+
return initFunction.count(t.getAsOpaquePointer()) == 0;
67+
}
68+
69+
bool isDropFunction(mlir::Type t, mlir::rlc::FunctionOp method)
70+
{
71+
return (
72+
method.getUnmangledName() == "drop" and
73+
returnsVoid(method.getType()).succeeded() and
74+
method.getType().getNumInputs() == 1 and
75+
method.getType().getInput(0) == t);
76+
}
77+
78+
bool isTriviallyDestructible(mlir::Type t)
79+
{
80+
return dropFunction.count(t.getAsOpaquePointer()) == 0;
81+
}
82+
83+
bool isAssignFunction(mlir::Type t, mlir::rlc::FunctionOp method)
84+
{
85+
return (
86+
method.getUnmangledName() == "assign" and
87+
returnsVoid(method.getType()).succeeded() and
88+
method.getType().getNumInputs() == 2 and
89+
method.getType().getInput(0) == t and
90+
method.getType().getInput(1) == t);
91+
}
92+
93+
bool isTriviallyCopiable(mlir::Type t)
94+
{
95+
return assignFunction.count(t.getAsOpaquePointer()) == 0;
96+
}
97+
98+
llvm::DenseSet<mlir::rlc::FunctionOp> getMemberFunctionsOf(mlir::Type type)
99+
{
100+
return typeToMethods[type.getAsOpaquePointer()];
101+
}
102+
103+
private:
104+
std::map<const void*, llvm::DenseSet<mlir::rlc::FunctionOp>> typeToMethods;
105+
std::map<const void*, mlir::rlc::FunctionOp> initFunction;
106+
std::map<const void*, mlir::rlc::FunctionOp> dropFunction;
107+
std::map<const void*, mlir::rlc::FunctionOp> assignFunction;
108+
};
109+
} // namespace mlir::rlc

lib/utils/include/rlc/utils/PatternMatcher.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ namespace mlir::rlc
3636
};
3737
} // namespace impl
3838

39+
/* *
40+
* A type serializer that can be configured with lambda functions.
41+
* The usefullnes of suing this over regular switch is that it caches
42+
* results so it does not recompute everything every time.
43+
* */
3944
class TypeSerializer
4045
{
4146
public:

0 commit comments

Comments
 (0)