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.