4 ms·
Julia is my language of choice for writing compilers. Quasiquoting means that codegen is as easy as string interpolation is in other languages. quote
by jamii 10y ago
Julia is my language of choice for writing compilers.
Quasiquoting means that codegen is as easy as string interpolation is in other languages.
quote
let
$(index_inits...)
$(results_inits...)
if $(reduce((a,b) -> :($a && $b), true, index_checks))
let
$(var_inits...) # declare vars local in here so they can't shadow relation names
$body
end
end
tuple($(results...))
end
end
Great introspection into the inference and compilation pipeline, directly from the repl.
julia> function double(xs)
[2*x for x in xs]
end
double (generic function with 1 method)
julia> double([1,2,3])
3-element Array{Int64,1}:
2
4
6
julia> @code_warntype double([1,2,3])
Variables:
xs::Array{Int64,1}
#s1::Int64
#s2::Int64
#s3::Int64
x::Int64
#s4::Int64
Body:
begin # none, line 2:
GenSym(1) = (Base.arraylen)(xs::Array{Int64,1})::Int64
0:
GenSym(3) = (top(ccall))(:jl_alloc_array_1d,(top(apply_type))(Base.Array,Int64,1)::Type{Array{Int64,1}},(top(svec))(Base.Any,Base.Int)::SimpleVector,Array{Int64,1},0,GenSym(1),0)::Array{Int64,1}
#s1 = 1
#s2 = 1
#s3 = 0
unless (Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)) goto 2
3:
#s3 = (Base.box)(Base.Int,(Base.add_int)(#s3::Int64,1))
GenSym(9) = (Base.arrayref)(xs::Array{Int64,1},#s2::Int64)::Int64
GenSym(10) = (Base.box)(Base.Int,(Base.add_int)(#s2::Int64,1))
#s4 = 1
GenSym(11) = GenSym(9)
GenSym(12) = (Base.box)(Base.Int,(Base.add_int)(1,1))
x = GenSym(11)
#s4 = GenSym(12)
GenSym(13) = GenSym(10)
GenSym(14) = (Base.box)(Base.Int,(Base.add_int)(2,1))
#s2 = GenSym(13)
#s4 = GenSym(14)
GenSym(4) = (Base.box)(Int64,(Base.mul_int)(2,x::Int64))
$(Expr(:type_goto, 0, GenSym(4)))
$(Expr(:boundscheck, false))
(Base.arrayset)(GenSym(3),GenSym(4),#s1::Int64)::Array{Int64,1}
$(Expr(:boundscheck, :(Main.pop)))
#s1 = (Base.box)(Base.Int,(Base.add_int)(#s1::Int64,1))
4:
unless (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)))) goto 3
2:
1:
return GenSym(3)
end::Array{Int64,1}
julia> @code_llvm double([1,2,3])
define %jl_value_t* @julia_double_21481(%jl_value_t*, %jl_value_t**, i32) {
top:
%3 = alloca [4 x %jl_value_t*], align 8
%.sub = getelementptr inbounds [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 0
%4 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 2
%5 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 3
%6 = bitcast [4 x %jl_value_t*]* %3 to i64*
store i64 4, i64* %6, align 8
%7 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 1
%8 = load i64, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
%9 = bitcast %jl_value_t** %7 to i64*
store i64 %8, i64* %9, align 8
store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8
store %jl_value_t* null, %jl_value_t** %4, align 8
store %jl_value_t* null, %jl_value_t** %5, align 8
%10 = load %jl_value_t*, %jl_value_t** %1, align 8
%11 = getelementptr inbounds %jl_value_t, %jl_value_t* %10, i64 1
%12 = bitcast %jl_value_t* %11 to i64*
%13 = load i64, i64* %12, align 8
store %jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), %jl_value_t** %5, align 8
%14 = call %jl_value_t* inttoptr (i64 140149385306688 to %jl_value_t* (%jl_value_t*, i64)*)(%jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), i64 inreg %13)
store %jl_value_t* %14, %jl_value_t** %4, align 8
%15 = icmp eq i64 %13, 0
br i1 %15, label %L4, label %L1.preheader
L1.preheader: ; preds = %top
%16 = load i64, i64* %12, align 8
%17 = bitcast %jl_value_t* %10 to i64**
%18 = bitcast %jl_value_t* %14 to i64**
br label %L1
L1: ; preds = %idxend, %L1.preheader
%"#s3.0" = phi i64 [ %22, %idxend ], [ 0, %L1.preheader ]
%"#s2.0" = phi i64 [ %26, %idxend ], [ 1, %L1.preheader ]
%19 = add i64 %"#s2.0", -1
%20 = icmp ult i64 %19, %16
br i1 %20, label %idxend, label %oob
oob: ; preds = %L1
%21 = alloca i64, align 8
store i64 %"#s2.0", i64* %21, align 8
call void @jl_bounds_error_ints(%jl_value_t* %10, i64* nonnull %21, i64 1)
unreachable
idxend: ; preds = %L1
%22 = add i64 %"#s3.0", 1
%23 = load i64*, i64** %17, align 8
%24 = getelementptr i64, i64* %23, i64 %19
%25 = load i64, i64* %24, align 8
%26 = add i64 %"#s2.0", 1
%27 = shl i64 %25, 1
%28 = load i64*, i64** %18, align 8
%29 = getelementptr i64, i64* %28, i64 %19
store i64 %27, i64* %29, align 8
%30 = icmp eq i64 %22, %13
br i1 %30, label %L4.loopexit, label %L1
L4.loopexit: ; preds = %idxend
br label %L4
L4: ; preds = %L4.loopexit, %top
%31 = load i64, i64* %9, align 8
store i64 %31, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
ret %jl_value_t* %14
}
julia> @code_native double([1,2,3])
L128:L159:L177: .text
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %rbx
subq $40, %rsp
movabsq $jl_alloc_array_1d, %rax
movabsq $140140655870352, %rdi # imm = 0x7F750A030190
movq $4, -56(%rbp)
movabsq $jl_pgcstack, %r15
movq (%r15), %rcx
movq %rcx, -48(%rbp)
leaq -56(%rbp), %rcx
movq %rcx, (%r15)
movq $0, -40(%rbp)
movq $0, -32(%rbp)
movq (%rsi), %r14
movq 8(%r14), %rbx
movq %rdi, -32(%rbp)
movq %rbx, %rsi
callq *%rax
movq %rax, -40(%rbp)
cmpq $0, %rbx
je L159
xorl %ecx, %ecx
movq 8(%r14), %rdx
nopw %cs:(%rax,%rax)
cmpq %rdx, %rcx
jae L177
movq (%r14), %rsi
movq (%rsi,%rcx,8), %rsi
shlq $1, %rsi
movq (%rax), %rdi
movq %rsi, (%rdi,%rcx,8)
incq %rcx
cmpq %rcx, %rbx
jne L128
movq -48(%rbp), %rcx
movq %rcx, (%r15)
leaq -24(%rbp), %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
movq %rsp, %rsi
addq $-16, %rsi
movq %rsi, %rsp
addq $1, %rcx
movq %rcx, (%rsi)
movabsq $jl_bounds_error_ints, %rax
movl $1, %edx
movq %r14, %rdi
callq *%rax
It catches type errors early, thanks to the typed multiple dispatch.
julia> xs = []
0-element Array{Any,1}
julia> push!(xs, 42)
1-element Array{Any,1}:
42
julia> push!(xs, "foo")
2-element Array{Any,1}:
42
"foo"
julia> ys = Int64[]
0-element Array{Int64,1}
julia> push!(ys, 42)
1-element Array{Int64,1}:
42
julia> push!(ys, "foo")
ERROR: MethodError: `convert` has no method matching convert(::Type{Int64}, ::ASCIIString)
This may have arisen from a call to the constructor Int64(...),
since type constructors fall back to convert methods.
Closest candidates are:
call{T}(::Type{T}, ::Any)
convert(::Type{Int64}, ::Int8)
convert(::Type{Int64}, ::UInt8)
...
in push! at ./array.jl:432
Plus, I only have to think in one language, but I can write sloppy dynamic heap-allocating-everywhere code in the compiler and with just a bit of thinking emit zero-allocation statically-dispatched code in the output.