-
Notifications
You must be signed in to change notification settings - Fork 107
/
Cxx.jl
246 lines (223 loc) · 8.99 KB
/
Cxx.jl
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# Cxx - The Julia C++ FFI
#
# This file contains the julia parts of the C++ FFI.
# For bootstrapping purposes, there is a small C++ shim
# that we will call out to. In general I try to keep the amount of
# code duplication on the julia side to a minimum, even if this
# means having more code on the C++ side (the original version
# had a messy 4 step bootstrap process).
#
# There are two ways to access the main functionality, provided by
# this package. The first is using the @cxx macro, which puns on
# julia syntax to provide C++ compatibility.
# The three basic features provided by the @cxx macro are:
#
# - Static function call
# @cxx mynamespace::func(args...)
# - Membercall (where m is a CppPtr, CppRef or CppValue)
# @cxx m->foo(args...)
# - Value Reference
# @cxx foo
#
# Note that unary * inside a call, e.g. @cxx foo(*(a)) [the () being necessary
# since julia syntax does not allow `*` in unary op position otherwise] is
# treated as a (C++ side) dereference of a. Further, prefixing any value by `&`
# takes the address of a given value.
#
# Additionally, this package provides the cxx"" and icxx"" custom
# string literals for inputting C++ syntax directly. The two string
# literals are distinguished by the C++ level scope they represent.
# The cxx"" literal evaluates the contained C++ code at global scope
# and can be used for declaring namespaces, classes, functions, global
# variables, etc. while the icxx"" evaluates the contained code at
# function scope and should be used for calling C++ functions or
# performing computations.
#
# # # High Level Overview
#
# The two primary Julia features that enable Cxx.jl to work are
# llvmcall and staged functions.
#
# # llvmcall
#
# llvmcall allows the user to pass in an LLVM IR expression which
# will then be embedded directly in the julia expressions. This
# functionality could be considered the `inline assembly` equivalent
# for julia. However, all optimizations are run after `llvmcall` IR
# has been inlined into the julia IR, all LLVM optimizations such
# as constant propagation, dead code elimination, etc. are applied
# across both sources of IR, eliminating a common inefficiency of
# using inline (machine code) assembly.
#
# The primary llvmcall syntax is as follows (reminiscent of the
# ccall syntax):
#
# llvmcall("""%3 = add i32 %1, %0
# ret i32 %3 """, Int32, (Int32, Int32), x, y)
#
# \________________________/ \_____/ \____________/ \___/
# Input LLVM IR | Argument Tuple |
# Return Type Argument
#
# Behind the scenes, LLVM will take the IR, wrap it in an LLVM function
# with the given return type and argument types. To call this function,
# julia does the same argument translation it would for a ccall (e.g.
# unboxing x and y if necessary). Afterwards, the resulting call instruction
# is inlined.
#
# In this package, however, we use the second form of llvmcall, which differs
# from the first inthat the IR argument is not a string, but a Ptr{Cvoid}. In
# this case, julia will skip the wrapping and proceed straight to argument
# translation and inlining.
#
# The underlying idea is thus simple: have Clang generate some
# LLVM IR in memory and then use the second form of LLVM IR to actually call it.
#
# # The @cxx macro
#
# The @cxx macro (see above for a description of its usage) thus needs to
# analyze the expression passed to it and generate an equivalent representation
# as a Clang AST, compile it and splice the resulting function pointer into
# llvmcall. In principle, this is quite straightforward. We simply need to
# match on the appropriate Julia AST and call the appropriate methods in Clang's
# Sema instance to generate the expression. And while it can be tricky to figure
# out what method to call, the real problem with this approach is types. Since
# C++ has compile time function overloading based on types, we need to know
# the argument types to call the function with, so we may select the correct to
# call. However, since @cxx is a macro, it operates on syntax only and,
# in particular, does not know the types of the expressions that form the
# parameters of the C++ function.
#
# The solution to this is to, as always in computing, add an extra layer of
# indirection.
#
# # Staged functions
#
# Staged function are similar to macros in that they return expressions rather
# than values. E.g.
#
# @generated function staged_t1(a,b)
# if a == Int
# return :(a+b)
# else
# return :(a*b)
# end
# end
# @test staged_t1(1,2) == 3 # a is an Int
# @test staged_t1(1.0,0.5) == 0.5 # a is a Float64 (i.e. not an Int)
# @test staged_t1(1,0.5) == 1.5 # a is an Int
#
# Though the example above could have of course been done using regular
# dispatch, it does illustrate the usage of staged functions: Instead of
# being passed the values, the staged function is first given the type of
# the argument and, after some computation, returns an expression that
# represents the actual body of the function to run.
#
# An important feature of staged functions is that, though a staged function
# may be called with abstract types, if the staged function throws an error
# when passed abstract types, execution of the staged function is delayed until
# all argument types are known.
#
# # Implementation of the @cxx macro
#
# We can thus see how macros and staged functions fit together. First, @cxx
# does some syntax transformation to make sure all the required information
# is available to the staged function, e.g.
#
# julia> :( @cxx foo(a,b) ) |> macroexpand
# :( cppcall( CppNNS{(:foo,)}(), a, b))
#
# Here cppcall is the staged function. Note that the name of the function to
# call was wrapped as the type parameter to a CppNNS type. This is important,
# because otherwise the staged function would not have access to the function
# name (since it's a symbol rather than a value). With this, cppcall will get
# the function name and the types of the two parameters, which is all it needs
# to build up the clang AST. The expression returned by the staged function
# will then simply be the `llvmcall` with the appropriate generated LLVM
# Function (in some cases we need to return some extra code, but those cases
# are discussed below)
#
__precompile__(true)
module Cxx
module CxxCore
pathfile = joinpath(dirname(@__FILE__), "..", "deps", "path.jl")
isfile(pathfile) || error("path.jl not generated. Try running Pkg.build(\"Cxx\")")
include(pathfile)
using Base.Meta
using Core: svec
using Base.Sys: isapple, isbsd, islinux, isunix, iswindows
# These are re-exported from Cxx
export cast,
@cxx_str, @cxx_mstr, @icxx_str, @icxx_mstr, @cxxt_str,
@cxx, @cxxnew, @jpcpp_str, @exception, @cxxm,
addHeaderDir, defineMacro, cxxinclude, cxxparse, new_clang_instance,
C_User, C_System, C_ExternCSystem
# These are internal but useful for hacking
export CppValue, CppRef, CppPtr, cpptype, CxxQualType, CppBaseType,
CppTemplate, CxxBuiltinTypes, CxxBuiltinTs, CxxException, CppFptr, CppMFptr
include("cxxtypes.jl")
include("clanginstances.jl")
include("clangwrapper.jl")
include("typetranslation.jl")
include("initialization.jl")
include("codegen.jl")
include("cxxmacro.jl")
include("cxxstr.jl")
include("utils.jl")
include("exceptions.jl")
# In precompilation mode, we do still need clang, so do it manually
__init__()
end
# Make these available as Cxx.
import .CxxCore: __default_compiler__, instance, compiler
# This is meant to be overriden before using Cxx in the current
# module if one wants to use a compiler with non standard options.
import .CxxCore: __current_compiler__
export __current_compiler__
# Re-export these
import .CxxCore: cast,
@cxx_str, @icxx_str, @cxxt_str,
@cxx, @cxxnew, @pcpp_str, @jpcpp_str, @exception, @cxxm,
addHeaderDir, defineMacro, cxxinclude, cxxparse, new_clang_instance,
C_User, C_System, C_ExternCSystem
export cast,
@cxx_str, @icxx_str, @cxxt_str,
@cxx, @cxxnew, @pcpp_str, @jpcpp_str, @exception, @cxxm,
addHeaderDir, defineMacro, cxxinclude, cxxparse, new_clang_instance,
C_User, C_System, C_ExternCSystem
include("CxxREPL/replpane.jl")
# C++ standard library helpers
module CxxStd
using ..CxxCore
using ..Cxx
include("show.jl")
include("autowrap.jl")
include("std.jl")
end
# Use as Cxx.
import .CxxStd: @list
module CxxREPLInit
using ..CxxCore
using ..CxxREPL
function __init__()
if isdefined(Base, :active_repl)
CxxREPL.RunCxxREPL(CxxCore.__current_compiler__)
end
end
end
module CxxExceptionInit
using ..CxxCore
__init__() = ccall(:jl_generating_output, Cint, ()) == 0 &&
eval(:(CxxCore.setup_exception_callback()))
end
module CxxDumpPCH
using ..CxxCore
# Now that we've loaded Cxx, save everything we just did into a PCH
if ccall(:jl_generating_output, Cint, ()) != 0
append!(CxxCore.GlobalPCHBuffer, CxxCore.decouple_pch(CxxCore.instance(CxxCore.__current_compiler__)))
end
end
if ccall(:jl_generating_output, Cint, ()) != 0
CxxCore.reset_init!()
end
end