2583 lines
84 KiB
Python
2583 lines
84 KiB
Python
|
import ctypes
|
||
|
import threading
|
||
|
from ctypes import CFUNCTYPE, c_int, c_int32
|
||
|
from ctypes.util import find_library
|
||
|
import gc
|
||
|
import locale
|
||
|
import os
|
||
|
import platform
|
||
|
import re
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import unittest
|
||
|
from contextlib import contextmanager
|
||
|
from tempfile import mkstemp
|
||
|
|
||
|
from llvmlite import ir
|
||
|
from llvmlite import binding as llvm
|
||
|
from llvmlite.binding import ffi
|
||
|
from llvmlite.tests import TestCase
|
||
|
|
||
|
|
||
|
# arvm7l needs extra ABI symbols to link successfully
|
||
|
if platform.machine() == 'armv7l':
|
||
|
llvm.load_library_permanently('libgcc_s.so.1')
|
||
|
|
||
|
|
||
|
def no_de_locale():
|
||
|
cur = locale.setlocale(locale.LC_ALL)
|
||
|
try:
|
||
|
locale.setlocale(locale.LC_ALL, 'de_DE')
|
||
|
except locale.Error:
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
finally:
|
||
|
locale.setlocale(locale.LC_ALL, cur)
|
||
|
|
||
|
|
||
|
asm_sum = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
source_filename = "asm_sum.c"
|
||
|
target triple = "{triple}"
|
||
|
%struct.glob_type = type {{ i64, [2 x i64]}}
|
||
|
%struct.glob_type_vec = type {{ i64, <2 x i64>}}
|
||
|
|
||
|
@glob = global i32 0
|
||
|
@glob_b = global i8 0
|
||
|
@glob_f = global float 1.5
|
||
|
@glob_struct = global %struct.glob_type {{i64 0, [2 x i64] [i64 0, i64 0]}}
|
||
|
|
||
|
define i32 @sum(i32 %.1, i32 %.2) {{
|
||
|
%.3 = add i32 %.1, %.2
|
||
|
%.4 = add i32 0, %.3
|
||
|
ret i32 %.4
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_sum2 = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define i32 @sum(i32 %.1, i32 %.2) {{
|
||
|
%.3 = add i32 %.1, %.2
|
||
|
ret i32 %.3
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_sum3 = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define i64 @sum(i64 %.1, i64 %.2) {{
|
||
|
%.3 = add i64 %.1, %.2
|
||
|
%.4 = add i64 5, %.3
|
||
|
%.5 = add i64 -5, %.4
|
||
|
ret i64 %.5
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_mul = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
@mul_glob = global i32 0
|
||
|
|
||
|
define i32 @mul(i32 %.1, i32 %.2) {{
|
||
|
%.3 = mul i32 %.1, %.2
|
||
|
ret i32 %.3
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_square_sum = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
@mul_glob = global i32 0
|
||
|
|
||
|
declare i32 @sum(i32, i32)
|
||
|
define i32 @square_sum(i32 %.1, i32 %.2) {{
|
||
|
%.3 = call i32 @sum(i32 %.1, i32 %.2)
|
||
|
%.4 = mul i32 %.3, %.3
|
||
|
ret i32 %.4
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_getversion = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
declare i8* @Py_GetVersion()
|
||
|
|
||
|
define void @getversion(i32 %.1, i32 %.2) {{
|
||
|
%1 = call i8* @Py_GetVersion()
|
||
|
ret void
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
if platform.python_implementation() == 'PyPy':
|
||
|
asm_getversion = asm_getversion.replace('Py_GetVersion', 'PyPy_GetVersion')
|
||
|
|
||
|
# `fadd` used on integer inputs
|
||
|
asm_parse_error = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define i32 @sum(i32 %.1, i32 %.2) {{
|
||
|
%.3 = fadd i32 %.1, %.2
|
||
|
ret i32 %.3
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
# "%.bug" definition references itself
|
||
|
asm_verification_fail = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @sum() {{
|
||
|
%.bug = add i32 1, %.bug
|
||
|
ret void
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_sum_declare = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
declare i32 @sum(i32 %.1, i32 %.2)
|
||
|
"""
|
||
|
|
||
|
asm_vararg_declare = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
declare i32 @vararg(i32 %.1, ...)
|
||
|
"""
|
||
|
|
||
|
asm_double_inaccurate = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @foo() {{
|
||
|
%const = fadd fp128 0xLF3CB1CCF26FBC178452FB4EC7F91DEAD, 0xL00000000000000000000000000000001
|
||
|
ret void
|
||
|
}}
|
||
|
""" # noqa E501
|
||
|
|
||
|
asm_double_locale = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @foo() {{
|
||
|
%const = fadd double 0.0, 3.14
|
||
|
ret void
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
|
||
|
asm_inlineasm = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @foo() {{
|
||
|
call void asm sideeffect "nop", ""()
|
||
|
ret void
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_inlineasm2 = """
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @inlineme() {{
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define i32 @caller(i32 %.1, i32 %.2) {{
|
||
|
entry:
|
||
|
%stack = alloca i32
|
||
|
store i32 %.1, i32* %stack
|
||
|
br label %main
|
||
|
main:
|
||
|
%loaded = load i32, i32* %stack
|
||
|
%.3 = add i32 %loaded, %.2
|
||
|
%.4 = add i32 0, %.3
|
||
|
call void @inlineme()
|
||
|
ret i32 %.4
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
asm_inlineasm3 = """
|
||
|
; ModuleID = 'test.c'
|
||
|
source_filename = "test.c"
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
; Function Attrs: noinline nounwind optnone ssp uwtable
|
||
|
define void @inlineme() noinline !dbg !15 {{
|
||
|
ret void, !dbg !18
|
||
|
}}
|
||
|
|
||
|
; Function Attrs: noinline nounwind optnone ssp uwtable
|
||
|
define i32 @foo(i32 %0, i32 %1) !dbg !19 {{
|
||
|
%3 = alloca i32, align 4
|
||
|
%4 = alloca i32, align 4
|
||
|
store i32 %0, i32* %3, align 4
|
||
|
call void @llvm.dbg.declare(metadata i32* %3, metadata !23, metadata !DIExpression()), !dbg !24
|
||
|
store i32 %1, i32* %4, align 4
|
||
|
call void @llvm.dbg.declare(metadata i32* %4, metadata !25, metadata !DIExpression()), !dbg !26
|
||
|
call void @inlineme(), !dbg !27
|
||
|
%5 = load i32, i32* %3, align 4, !dbg !28
|
||
|
%6 = load i32, i32* %4, align 4, !dbg !29
|
||
|
%7 = add nsw i32 %5, %6, !dbg !30
|
||
|
ret i32 %7, !dbg !31
|
||
|
}}
|
||
|
|
||
|
; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
|
||
|
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
|
||
|
|
||
|
attributes #1 = {{ nofree nosync nounwind readnone speculatable willreturn }}
|
||
|
|
||
|
!llvm.module.flags = !{{!1, !2, !3, !4, !5, !6, !7, !8, !9, !10}}
|
||
|
!llvm.dbg.cu = !{{!11}}
|
||
|
!llvm.ident = !{{!14}}
|
||
|
|
||
|
!0 = !{{i32 2, !"SDK Version", [2 x i32] [i32 12, i32 3]}}
|
||
|
!1 = !{{i32 7, !"Dwarf Version", i32 4}}
|
||
|
!2 = !{{i32 2, !"Debug Info Version", i32 3}}
|
||
|
!3 = !{{i32 1, !"wchar_size", i32 4}}
|
||
|
!4 = !{{i32 1, !"branch-target-enforcement", i32 0}}
|
||
|
!5 = !{{i32 1, !"sign-return-address", i32 0}}
|
||
|
!6 = !{{i32 1, !"sign-return-address-all", i32 0}}
|
||
|
!7 = !{{i32 1, !"sign-return-address-with-bkey", i32 0}}
|
||
|
!8 = !{{i32 7, !"PIC Level", i32 2}}
|
||
|
!9 = !{{i32 7, !"uwtable", i32 1}}
|
||
|
!10 = !{{i32 7, !"frame-pointer", i32 1}}
|
||
|
!11 = distinct !DICompileUnit(language: DW_LANG_C99, file: !12, producer: "Apple clang version 13.1.6 (clang-1316.0.21.2.3)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !13, splitDebugInlining: false, nameTableKind: None, sysroot: "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk", sdk: "MacOSX.sdk")
|
||
|
!12 = !DIFile(filename: "test.c", directory: "/")
|
||
|
!13 = !{{}}
|
||
|
!14 = !{{!"Apple clang version 13.1.6 (clang-1316.0.21.2.3)"}}
|
||
|
!15 = distinct !DISubprogram(name: "inlineme", scope: !12, file: !12, line: 1, type: !16, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !11, retainedNodes: !13)
|
||
|
!16 = !DISubroutineType(types: !17)
|
||
|
!17 = !{{null}}
|
||
|
!18 = !DILocation(line: 1, column: 22, scope: !15)
|
||
|
!19 = distinct !DISubprogram(name: "foo", scope: !12, file: !12, line: 3, type: !20, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !11, retainedNodes: !13)
|
||
|
!20 = !DISubroutineType(types: !21)
|
||
|
!21 = !{{!22, !22, !22}}
|
||
|
!22 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
|
||
|
!23 = !DILocalVariable(name: "a", arg: 1, scope: !19, file: !12, line: 3, type: !22)
|
||
|
!24 = !DILocation(line: 3, column: 13, scope: !19)
|
||
|
!25 = !DILocalVariable(name: "b", arg: 2, scope: !19, file: !12, line: 3, type: !22)
|
||
|
!26 = !DILocation(line: 3, column: 20, scope: !19)
|
||
|
!27 = !DILocation(line: 4, column: 5, scope: !19)
|
||
|
!28 = !DILocation(line: 5, column: 12, scope: !19)
|
||
|
!29 = !DILocation(line: 5, column: 16, scope: !19)
|
||
|
!30 = !DILocation(line: 5, column: 14, scope: !19)
|
||
|
!31 = !DILocation(line: 5, column: 5, scope: !19)
|
||
|
""" # noqa E501
|
||
|
|
||
|
licm_asm = r"""
|
||
|
; ModuleID = "<string>"
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define double @licm(i32 %0) {{
|
||
|
%2 = alloca i32, align 4
|
||
|
%3 = alloca double, align 8
|
||
|
%4 = alloca i32, align 4
|
||
|
%5 = alloca double, align 8
|
||
|
store i32 %0, i32* %2, align 4
|
||
|
store double 0.000000e+00, double* %3, align 8
|
||
|
store i32 0, i32* %4, align 4
|
||
|
br label %6
|
||
|
|
||
|
6: ; preds = %14, %1
|
||
|
%7 = load i32, i32* %4, align 4
|
||
|
%8 = load i32, i32* %2, align 4
|
||
|
%9 = icmp slt i32 %7, %8
|
||
|
br i1 %9, label %10, label %17
|
||
|
|
||
|
10: ; preds = %6
|
||
|
store double 7.000000e+00, double* %5, align 8
|
||
|
%11 = load double, double* %5, align 8
|
||
|
%12 = load double, double* %3, align 8
|
||
|
%13 = fadd double %12, %11
|
||
|
store double %13, double* %3, align 8
|
||
|
br label %14
|
||
|
|
||
|
14: ; preds = %10
|
||
|
%15 = load i32, i32* %4, align 4
|
||
|
%16 = add nsw i32 %15, 1
|
||
|
store i32 %16, i32* %4, align 4
|
||
|
br label %6
|
||
|
|
||
|
17: ; preds = %6
|
||
|
%18 = load double, double* %3, align 8
|
||
|
ret double %18
|
||
|
}}
|
||
|
""" # noqa E501
|
||
|
|
||
|
asm_global_ctors = r"""
|
||
|
; ModuleID = "<string>"
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
@A = global i32 undef
|
||
|
|
||
|
define void @ctor_A()
|
||
|
{{
|
||
|
store i32 10, i32* @A
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define void @dtor_A()
|
||
|
{{
|
||
|
store i32 20, i32* @A
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define i32 @foo()
|
||
|
{{
|
||
|
%.2 = load i32, i32* @A
|
||
|
%.3 = add i32 %.2, 2
|
||
|
ret i32 %.3
|
||
|
}}
|
||
|
|
||
|
@llvm.global_ctors = appending global [1 x {{i32, void ()*, i8*}}] [{{i32, void ()*, i8*}} {{i32 0, void ()* @ctor_A, i8* null}}]
|
||
|
@llvm.global_dtors = appending global [1 x {{i32, void ()*, i8*}}] [{{i32, void ()*, i8*}} {{i32 0, void ()* @dtor_A, i8* null}}]
|
||
|
""" # noqa E501
|
||
|
|
||
|
asm_ext_ctors = r"""
|
||
|
; ModuleID = "<string>"
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
@A = external global i32
|
||
|
|
||
|
define void @ctor_A()
|
||
|
{{
|
||
|
store i32 10, i32* @A
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define void @dtor_A()
|
||
|
{{
|
||
|
store i32 20, i32* @A
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define i32 @foo()
|
||
|
{{
|
||
|
%.2 = load i32, i32* @A
|
||
|
%.3 = add i32 %.2, 2
|
||
|
ret i32 %.3
|
||
|
}}
|
||
|
|
||
|
@llvm.global_ctors = appending global [1 x {{i32, void ()*, i8*}}] [{{i32, void ()*, i8*}} {{i32 0, void ()* @ctor_A, i8* null}}]
|
||
|
@llvm.global_dtors = appending global [1 x {{i32, void ()*, i8*}}] [{{i32, void ()*, i8*}} {{i32 0, void ()* @dtor_A, i8* null}}]
|
||
|
""" # noqa E501
|
||
|
|
||
|
|
||
|
asm_nonalphanum_blocklabel = """; ModuleID = ""
|
||
|
target triple = "unknown-unknown-unknown"
|
||
|
target datalayout = ""
|
||
|
|
||
|
define i32 @"foo"()
|
||
|
{
|
||
|
"<>!*''#":
|
||
|
ret i32 12345
|
||
|
}
|
||
|
""" # noqa W291 # trailing space needed for match later
|
||
|
|
||
|
|
||
|
asm_null_constant = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @foo(i64* %.1) {{
|
||
|
ret void
|
||
|
}}
|
||
|
|
||
|
define void @bar() {{
|
||
|
call void @foo(i64* null)
|
||
|
ret void
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
|
||
|
riscv_asm_ilp32 = [
|
||
|
'addi\tsp, sp, -16',
|
||
|
'sw\ta1, 8(sp)',
|
||
|
'sw\ta2, 12(sp)',
|
||
|
'fld\tft0, 8(sp)',
|
||
|
'fmv.w.x\tft1, a0',
|
||
|
'fcvt.d.s\tft1, ft1',
|
||
|
'fadd.d\tft0, ft1, ft0',
|
||
|
'fsd\tft0, 8(sp)',
|
||
|
'lw\ta0, 8(sp)',
|
||
|
'lw\ta1, 12(sp)',
|
||
|
'addi\tsp, sp, 16',
|
||
|
'ret'
|
||
|
]
|
||
|
|
||
|
|
||
|
riscv_asm_ilp32f = [
|
||
|
'addi\tsp, sp, -16',
|
||
|
'sw\ta0, 8(sp)',
|
||
|
'sw\ta1, 12(sp)',
|
||
|
'fld\tft0, 8(sp)',
|
||
|
'fcvt.d.s\tft1, fa0',
|
||
|
'fadd.d\tft0, ft1, ft0',
|
||
|
'fsd\tft0, 8(sp)',
|
||
|
'lw\ta0, 8(sp)',
|
||
|
'lw\ta1, 12(sp)',
|
||
|
'addi\tsp, sp, 16',
|
||
|
'ret'
|
||
|
]
|
||
|
|
||
|
|
||
|
riscv_asm_ilp32d = [
|
||
|
'fcvt.d.s\tft0, fa0',
|
||
|
'fadd.d\tfa0, ft0, fa1',
|
||
|
'ret'
|
||
|
]
|
||
|
|
||
|
|
||
|
asm_attributes = r"""
|
||
|
declare void @a_readonly_func(i8 *) readonly
|
||
|
|
||
|
declare i8* @a_arg0_return_func(i8* returned, i32*)
|
||
|
"""
|
||
|
|
||
|
|
||
|
# This produces the following output from objdump:
|
||
|
#
|
||
|
# $ objdump -D 632.elf
|
||
|
#
|
||
|
# 632.elf: file format elf64-x86-64
|
||
|
#
|
||
|
#
|
||
|
# Disassembly of section .text:
|
||
|
#
|
||
|
# 0000000000000000 <__arybo>:
|
||
|
# 0: 48 c1 e2 20 shl $0x20,%rdx
|
||
|
# 4: 48 09 c2 or %rax,%rdx
|
||
|
# 7: 48 89 d0 mov %rdx,%rax
|
||
|
# a: 48 c1 c0 3d rol $0x3d,%rax
|
||
|
# e: 48 31 d0 xor %rdx,%rax
|
||
|
# 11: 48 b9 01 20 00 04 80 movabs $0x7010008004002001,%rcx
|
||
|
# 18: 00 10 70
|
||
|
# 1b: 48 0f af c8 imul %rax,%rcx
|
||
|
|
||
|
issue_632_elf = \
|
||
|
"7f454c4602010100000000000000000001003e00010000000000000000000000000000" \
|
||
|
"0000000000e0000000000000000000000040000000000040000500010048c1e2204809" \
|
||
|
"c24889d048c1c03d4831d048b90120000480001070480fafc800000000000000000000" \
|
||
|
"0000000000000000000000000000002f0000000400f1ff000000000000000000000000" \
|
||
|
"00000000070000001200020000000000000000001f00000000000000002e7465787400" \
|
||
|
"5f5f617279626f002e6e6f74652e474e552d737461636b002e737472746162002e7379" \
|
||
|
"6d746162003c737472696e673e00000000000000000000000000000000000000000000" \
|
||
|
"0000000000000000000000000000000000000000000000000000000000000000000000" \
|
||
|
"00000000000000001f0000000300000000000000000000000000000000000000a80000" \
|
||
|
"0000000000380000000000000000000000000000000100000000000000000000000000" \
|
||
|
"000001000000010000000600000000000000000000000000000040000000000000001f" \
|
||
|
"000000000000000000000000000000100000000000000000000000000000000f000000" \
|
||
|
"01000000000000000000000000000000000000005f0000000000000000000000000000" \
|
||
|
"0000000000000000000100000000000000000000000000000027000000020000000000" \
|
||
|
"0000000000000000000000000000600000000000000048000000000000000100000002" \
|
||
|
"00000008000000000000001800000000000000"
|
||
|
|
||
|
|
||
|
issue_632_text = \
|
||
|
"48c1e2204809c24889d048c1c03d4831d048b90120000480001070480fafc8"
|
||
|
|
||
|
|
||
|
asm_tli_exp2 = r"""
|
||
|
; ModuleID = '<lambda>'
|
||
|
source_filename = "<string>"
|
||
|
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||
|
target triple = "x86_64-pc-windows-msvc"
|
||
|
|
||
|
declare float @llvm.exp2.f32(float %casted)
|
||
|
|
||
|
define float @foo(i16 %arg) {
|
||
|
entry:
|
||
|
%casted = sitofp i16 %arg to float
|
||
|
%ret = call float @llvm.exp2.f32(float %casted)
|
||
|
ret float %ret
|
||
|
}
|
||
|
""" # noqa E501
|
||
|
|
||
|
asm_phi_blocks = r"""
|
||
|
; ModuleID = '<string>'
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
define void @foo(i32 %N) {{
|
||
|
; unnamed block for testing
|
||
|
%cmp4 = icmp sgt i32 %N, 0
|
||
|
br i1 %cmp4, label %for.body, label %for.cond.cleanup
|
||
|
|
||
|
for.cond.cleanup:
|
||
|
ret void
|
||
|
|
||
|
for.body:
|
||
|
%i.05 = phi i32 [ %inc, %for.body ], [ 0, %0 ]
|
||
|
%inc = add nuw nsw i32 %i.05, 1
|
||
|
%exitcond.not = icmp eq i32 %inc, %N
|
||
|
br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
|
||
|
class BaseTest(TestCase):
|
||
|
|
||
|
def setUp(self):
|
||
|
llvm.initialize()
|
||
|
llvm.initialize_native_target()
|
||
|
llvm.initialize_native_asmprinter()
|
||
|
gc.collect()
|
||
|
self.old_garbage = gc.garbage[:]
|
||
|
gc.garbage[:] = []
|
||
|
|
||
|
def tearDown(self):
|
||
|
# Test that no uncollectable objects were created
|
||
|
# (llvmlite objects have a __del__ so a reference cycle could
|
||
|
# create some).
|
||
|
gc.collect()
|
||
|
self.assertEqual(gc.garbage, [])
|
||
|
# This will probably put any existing garbage in gc.garbage again
|
||
|
del self.old_garbage
|
||
|
|
||
|
def module(self, asm=asm_sum, context=None):
|
||
|
asm = asm.format(triple=llvm.get_default_triple())
|
||
|
mod = llvm.parse_assembly(asm, context)
|
||
|
return mod
|
||
|
|
||
|
def glob(self, name='glob', mod=None):
|
||
|
if mod is None:
|
||
|
mod = self.module()
|
||
|
return mod.get_global_variable(name)
|
||
|
|
||
|
def target_machine(self, *, jit):
|
||
|
target = llvm.Target.from_default_triple()
|
||
|
return target.create_target_machine(jit=jit)
|
||
|
|
||
|
|
||
|
class TestDependencies(BaseTest):
|
||
|
"""
|
||
|
Test DLL dependencies are within a certain expected set.
|
||
|
"""
|
||
|
|
||
|
@unittest.skipUnless(sys.platform.startswith('linux'),
|
||
|
"Linux-specific test")
|
||
|
@unittest.skipUnless(os.environ.get('LLVMLITE_DIST_TEST'),
|
||
|
"Distribution-specific test")
|
||
|
def test_linux(self):
|
||
|
lib_path = ffi.lib._name
|
||
|
env = os.environ.copy()
|
||
|
env['LANG'] = 'C'
|
||
|
p = subprocess.Popen(["objdump", "-p", lib_path],
|
||
|
stdout=subprocess.PIPE, env=env)
|
||
|
out, _ = p.communicate()
|
||
|
self.assertEqual(0, p.returncode)
|
||
|
# Parse library dependencies
|
||
|
lib_pat = re.compile(r'^([+-_a-zA-Z0-9]+)\.so(?:\.\d+){0,3}$')
|
||
|
deps = set()
|
||
|
for line in out.decode().splitlines():
|
||
|
parts = line.split()
|
||
|
if parts and parts[0] == 'NEEDED':
|
||
|
dep = parts[1]
|
||
|
m = lib_pat.match(dep)
|
||
|
if len(parts) != 2 or not m:
|
||
|
self.fail("invalid NEEDED line: %r" % (line,))
|
||
|
deps.add(m.group(1))
|
||
|
# Sanity check that our dependencies were parsed ok
|
||
|
if 'libc' not in deps or 'libpthread' not in deps:
|
||
|
self.fail("failed parsing dependencies? got %r" % (deps,))
|
||
|
# Ensure all dependencies are expected
|
||
|
allowed = set(['librt', 'libdl', 'libpthread', 'libz', 'libm',
|
||
|
'libgcc_s', 'libc', 'ld-linux', 'ld64'])
|
||
|
if platform.python_implementation() == 'PyPy':
|
||
|
allowed.add('libtinfo')
|
||
|
|
||
|
for dep in deps:
|
||
|
if not dep.startswith('ld-linux-') and dep not in allowed:
|
||
|
self.fail("unexpected dependency %r in %r" % (dep, deps))
|
||
|
|
||
|
|
||
|
class TestRISCVABI(BaseTest):
|
||
|
"""
|
||
|
Test calling convention of floating point arguments of RISC-V
|
||
|
using different ABI.
|
||
|
"""
|
||
|
triple = "riscv32-unknown-linux"
|
||
|
|
||
|
def setUp(self):
|
||
|
super().setUp()
|
||
|
llvm.initialize_all_targets()
|
||
|
llvm.initialize_all_asmprinters()
|
||
|
|
||
|
def check_riscv_target(self):
|
||
|
try:
|
||
|
llvm.Target.from_triple(self.triple)
|
||
|
except RuntimeError as e:
|
||
|
if "No available targets are compatible with triple" in str(e):
|
||
|
self.skipTest("RISCV target unsupported by linked LLVM.")
|
||
|
else:
|
||
|
raise e
|
||
|
|
||
|
def riscv_target_machine(self, **kwarg):
|
||
|
lltarget = llvm.Target.from_triple(self.triple)
|
||
|
return lltarget.create_target_machine(**kwarg)
|
||
|
|
||
|
def fpadd_ll_module(self):
|
||
|
f64 = ir.DoubleType()
|
||
|
f32 = ir.FloatType()
|
||
|
fnty = ir.FunctionType(f64, (f32, f64))
|
||
|
module = ir.Module()
|
||
|
func = ir.Function(module, fnty, name="fpadd")
|
||
|
block = func.append_basic_block()
|
||
|
builder = ir.IRBuilder(block)
|
||
|
a, b = func.args
|
||
|
arg0 = builder.fpext(a, f64)
|
||
|
result = builder.fadd(arg0, b)
|
||
|
builder.ret(result)
|
||
|
|
||
|
llmod = llvm.parse_assembly(str(module))
|
||
|
llmod.verify()
|
||
|
return llmod
|
||
|
|
||
|
def break_up_asm(self, asm):
|
||
|
asm_list = []
|
||
|
for line in asm.splitlines():
|
||
|
s_line = line.strip()
|
||
|
if not (s_line.startswith(".") or s_line.startswith("fpadd")
|
||
|
or s_line == ""):
|
||
|
asm_list.append(s_line)
|
||
|
return asm_list
|
||
|
|
||
|
def test_rv32d_ilp32(self):
|
||
|
self.check_riscv_target()
|
||
|
llmod = self.fpadd_ll_module()
|
||
|
target = self.riscv_target_machine(features="+f,+d")
|
||
|
self.assertEqual(self.break_up_asm(target.emit_assembly(llmod)),
|
||
|
riscv_asm_ilp32)
|
||
|
|
||
|
def test_rv32d_ilp32f(self):
|
||
|
self.check_riscv_target()
|
||
|
llmod = self.fpadd_ll_module()
|
||
|
target = self.riscv_target_machine(features="+f,+d", abiname="ilp32f")
|
||
|
self.assertEqual(self.break_up_asm(target.emit_assembly(llmod)),
|
||
|
riscv_asm_ilp32f)
|
||
|
|
||
|
def test_rv32d_ilp32d(self):
|
||
|
self.check_riscv_target()
|
||
|
llmod = self.fpadd_ll_module()
|
||
|
target = self.riscv_target_machine(features="+f,+d", abiname="ilp32d")
|
||
|
self.assertEqual(self.break_up_asm(target.emit_assembly(llmod)),
|
||
|
riscv_asm_ilp32d)
|
||
|
|
||
|
|
||
|
class TestMisc(BaseTest):
|
||
|
"""
|
||
|
Test miscellaneous functions in llvm.binding.
|
||
|
"""
|
||
|
|
||
|
def test_parse_assembly(self):
|
||
|
self.module(asm_sum)
|
||
|
|
||
|
def test_parse_assembly_error(self):
|
||
|
with self.assertRaises(RuntimeError) as cm:
|
||
|
self.module(asm_parse_error)
|
||
|
s = str(cm.exception)
|
||
|
self.assertIn("parsing error", s)
|
||
|
self.assertIn("invalid operand type", s)
|
||
|
|
||
|
def test_nonalphanum_block_name(self):
|
||
|
mod = ir.Module()
|
||
|
ft = ir.FunctionType(ir.IntType(32), [])
|
||
|
fn = ir.Function(mod, ft, "foo")
|
||
|
bd = ir.IRBuilder(fn.append_basic_block(name="<>!*''#"))
|
||
|
bd.ret(ir.Constant(ir.IntType(32), 12345))
|
||
|
asm = str(mod)
|
||
|
self.assertEqual(asm, asm_nonalphanum_blocklabel)
|
||
|
|
||
|
def test_global_context(self):
|
||
|
gcontext1 = llvm.context.get_global_context()
|
||
|
gcontext2 = llvm.context.get_global_context()
|
||
|
assert gcontext1 == gcontext2
|
||
|
|
||
|
def test_dylib_symbols(self):
|
||
|
llvm.add_symbol("__xyzzy", 1234)
|
||
|
llvm.add_symbol("__xyzzy", 5678)
|
||
|
addr = llvm.address_of_symbol("__xyzzy")
|
||
|
self.assertEqual(addr, 5678)
|
||
|
addr = llvm.address_of_symbol("__foobar")
|
||
|
self.assertIs(addr, None)
|
||
|
|
||
|
def test_get_default_triple(self):
|
||
|
triple = llvm.get_default_triple()
|
||
|
self.assertIsInstance(triple, str)
|
||
|
self.assertTrue(triple)
|
||
|
|
||
|
def test_get_process_triple(self):
|
||
|
# Sometimes we get synonyms for PPC
|
||
|
def normalize_ppc(arch):
|
||
|
if arch == 'powerpc64le':
|
||
|
return 'ppc64le'
|
||
|
else:
|
||
|
return arch
|
||
|
|
||
|
triple = llvm.get_process_triple()
|
||
|
default = llvm.get_default_triple()
|
||
|
self.assertIsInstance(triple, str)
|
||
|
self.assertTrue(triple)
|
||
|
|
||
|
default_arch = normalize_ppc(default.split('-')[0])
|
||
|
triple_arch = normalize_ppc(triple.split('-')[0])
|
||
|
# Arch must be equal
|
||
|
self.assertEqual(default_arch, triple_arch)
|
||
|
|
||
|
def test_get_host_cpu_features(self):
|
||
|
features = llvm.get_host_cpu_features()
|
||
|
# Check the content of `features`
|
||
|
self.assertIsInstance(features, dict)
|
||
|
self.assertIsInstance(features, llvm.FeatureMap)
|
||
|
for k, v in features.items():
|
||
|
self.assertIsInstance(k, str)
|
||
|
self.assertTrue(k) # single feature string cannot be empty
|
||
|
self.assertIsInstance(v, bool)
|
||
|
self.assertIsInstance(features.flatten(), str)
|
||
|
|
||
|
re_term = r"[+\-][a-zA-Z0-9\._-]+"
|
||
|
regex = r"^({0}|{0}(,{0})*)?$".format(re_term)
|
||
|
# quick check for our regex
|
||
|
self.assertIsNotNone(re.match(regex, ""))
|
||
|
self.assertIsNotNone(re.match(regex, "+aa"))
|
||
|
self.assertIsNotNone(re.match(regex, "+a,-bb"))
|
||
|
# check CpuFeature.flatten()
|
||
|
if len(features) == 0:
|
||
|
self.assertEqual(features.flatten(), "")
|
||
|
else:
|
||
|
self.assertIsNotNone(re.match(regex, features.flatten()))
|
||
|
|
||
|
def test_get_host_cpu_name(self):
|
||
|
cpu = llvm.get_host_cpu_name()
|
||
|
self.assertIsInstance(cpu, str)
|
||
|
self.assertTrue(cpu)
|
||
|
|
||
|
def test_initfini(self):
|
||
|
code = """if 1:
|
||
|
from llvmlite import binding as llvm
|
||
|
|
||
|
llvm.initialize()
|
||
|
llvm.initialize_native_target()
|
||
|
llvm.initialize_native_asmprinter()
|
||
|
llvm.initialize_all_targets()
|
||
|
llvm.initialize_all_asmprinters()
|
||
|
llvm.shutdown()
|
||
|
"""
|
||
|
subprocess.check_call([sys.executable, "-c", code])
|
||
|
|
||
|
def test_set_option(self):
|
||
|
# We cannot set an option multiple times (LLVM would exit() the
|
||
|
# process), so run the code in a subprocess.
|
||
|
code = """if 1:
|
||
|
from llvmlite import binding as llvm
|
||
|
|
||
|
llvm.set_option("progname", "-debug-pass=Disabled")
|
||
|
"""
|
||
|
subprocess.check_call([sys.executable, "-c", code])
|
||
|
|
||
|
def test_version(self):
|
||
|
major, minor, patch = llvm.llvm_version_info
|
||
|
# one of these can be valid
|
||
|
valid = [(14, )]
|
||
|
self.assertIn((major,), valid)
|
||
|
self.assertIn(patch, range(10))
|
||
|
|
||
|
def test_check_jit_execution(self):
|
||
|
llvm.check_jit_execution()
|
||
|
|
||
|
@unittest.skipIf(no_de_locale(), "Locale not available")
|
||
|
def test_print_double_locale(self):
|
||
|
m = self.module(asm_double_locale)
|
||
|
expect = str(m)
|
||
|
# Change the locale so that comma is used as decimal-point
|
||
|
# to trigger the LLVM bug (llvmlite issue #80)
|
||
|
locale.setlocale(locale.LC_ALL, 'de_DE')
|
||
|
# The LLVM bug is trigged by print the module with double constant
|
||
|
got = str(m)
|
||
|
# Changing the locale should not affect the LLVM IR
|
||
|
self.assertEqual(expect, got)
|
||
|
|
||
|
def test_no_accidental_warnings(self):
|
||
|
code = "from llvmlite import binding"
|
||
|
flags = "-Werror"
|
||
|
cmdargs = [sys.executable, flags, "-c", code]
|
||
|
subprocess.check_call(cmdargs)
|
||
|
|
||
|
|
||
|
class TestModuleRef(BaseTest):
|
||
|
|
||
|
def test_str(self):
|
||
|
mod = self.module()
|
||
|
s = str(mod).strip()
|
||
|
self.assertTrue(s.startswith('; ModuleID ='), s)
|
||
|
|
||
|
def test_close(self):
|
||
|
mod = self.module()
|
||
|
str(mod)
|
||
|
mod.close()
|
||
|
with self.assertRaises(ctypes.ArgumentError):
|
||
|
str(mod)
|
||
|
mod.close()
|
||
|
|
||
|
def test_with(self):
|
||
|
mod = self.module()
|
||
|
str(mod)
|
||
|
with mod:
|
||
|
str(mod)
|
||
|
with self.assertRaises(ctypes.ArgumentError):
|
||
|
str(mod)
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
with mod:
|
||
|
pass
|
||
|
|
||
|
def test_name(self):
|
||
|
mod = self.module()
|
||
|
mod.name = "foo"
|
||
|
self.assertEqual(mod.name, "foo")
|
||
|
mod.name = "bar"
|
||
|
self.assertEqual(mod.name, "bar")
|
||
|
|
||
|
def test_source_file(self):
|
||
|
mod = self.module()
|
||
|
self.assertEqual(mod.source_file, "asm_sum.c")
|
||
|
|
||
|
def test_data_layout(self):
|
||
|
mod = self.module()
|
||
|
s = mod.data_layout
|
||
|
self.assertIsInstance(s, str)
|
||
|
mod.data_layout = s
|
||
|
self.assertEqual(s, mod.data_layout)
|
||
|
|
||
|
def test_triple(self):
|
||
|
mod = self.module()
|
||
|
s = mod.triple
|
||
|
self.assertEqual(s, llvm.get_default_triple())
|
||
|
mod.triple = ''
|
||
|
self.assertEqual(mod.triple, '')
|
||
|
|
||
|
def test_verify(self):
|
||
|
# Verify successful
|
||
|
mod = self.module()
|
||
|
self.assertIs(mod.verify(), None)
|
||
|
# Verify failed
|
||
|
mod = self.module(asm_verification_fail)
|
||
|
with self.assertRaises(RuntimeError) as cm:
|
||
|
mod.verify()
|
||
|
s = str(cm.exception)
|
||
|
self.assertIn("%.bug = add i32 1, %.bug", s)
|
||
|
|
||
|
def test_get_function(self):
|
||
|
mod = self.module()
|
||
|
fn = mod.get_function("sum")
|
||
|
self.assertIsInstance(fn, llvm.ValueRef)
|
||
|
self.assertEqual(fn.name, "sum")
|
||
|
|
||
|
with self.assertRaises(NameError):
|
||
|
mod.get_function("foo")
|
||
|
|
||
|
# Check that fn keeps the module instance alive
|
||
|
del mod
|
||
|
str(fn.module)
|
||
|
|
||
|
def test_get_struct_type(self):
|
||
|
mod = self.module()
|
||
|
st_ty = mod.get_struct_type("struct.glob_type")
|
||
|
self.assertEqual(st_ty.name, "struct.glob_type")
|
||
|
# also match struct names of form "%struct.glob_type.{some_index}"
|
||
|
self.assertIsNotNone(re.match(
|
||
|
r'%struct\.glob_type(\.[\d]+)? = type { i64, \[2 x i64\] }',
|
||
|
str(st_ty)))
|
||
|
|
||
|
with self.assertRaises(NameError):
|
||
|
mod.get_struct_type("struct.doesnt_exist")
|
||
|
|
||
|
def test_get_global_variable(self):
|
||
|
mod = self.module()
|
||
|
gv = mod.get_global_variable("glob")
|
||
|
self.assertIsInstance(gv, llvm.ValueRef)
|
||
|
self.assertEqual(gv.name, "glob")
|
||
|
|
||
|
with self.assertRaises(NameError):
|
||
|
mod.get_global_variable("bar")
|
||
|
|
||
|
# Check that gv keeps the module instance alive
|
||
|
del mod
|
||
|
str(gv.module)
|
||
|
|
||
|
def test_global_variables(self):
|
||
|
mod = self.module()
|
||
|
it = mod.global_variables
|
||
|
del mod
|
||
|
globs = sorted(it, key=lambda value: value.name)
|
||
|
self.assertEqual(len(globs), 4)
|
||
|
self.assertEqual([g.name for g in globs],
|
||
|
["glob", "glob_b", "glob_f", "glob_struct"])
|
||
|
|
||
|
def test_functions(self):
|
||
|
mod = self.module()
|
||
|
it = mod.functions
|
||
|
del mod
|
||
|
funcs = list(it)
|
||
|
self.assertEqual(len(funcs), 1)
|
||
|
self.assertEqual(funcs[0].name, "sum")
|
||
|
|
||
|
def test_structs(self):
|
||
|
mod = self.module()
|
||
|
it = mod.struct_types
|
||
|
del mod
|
||
|
structs = list(it)
|
||
|
self.assertEqual(len(structs), 1)
|
||
|
self.assertIsNotNone(re.match(r'struct\.glob_type(\.[\d]+)?',
|
||
|
structs[0].name))
|
||
|
self.assertIsNotNone(re.match(
|
||
|
r'%struct\.glob_type(\.[\d]+)? = type { i64, \[2 x i64\] }',
|
||
|
str(structs[0])))
|
||
|
|
||
|
def test_link_in(self):
|
||
|
dest = self.module()
|
||
|
src = self.module(asm_mul)
|
||
|
dest.link_in(src)
|
||
|
self.assertEqual(
|
||
|
sorted(f.name for f in dest.functions), ["mul", "sum"])
|
||
|
dest.get_function("mul")
|
||
|
dest.close()
|
||
|
with self.assertRaises(ctypes.ArgumentError):
|
||
|
src.get_function("mul")
|
||
|
|
||
|
def test_link_in_preserve(self):
|
||
|
dest = self.module()
|
||
|
src2 = self.module(asm_mul)
|
||
|
dest.link_in(src2, preserve=True)
|
||
|
self.assertEqual(
|
||
|
sorted(f.name for f in dest.functions), ["mul", "sum"])
|
||
|
dest.close()
|
||
|
self.assertEqual(sorted(f.name for f in src2.functions), ["mul"])
|
||
|
src2.get_function("mul")
|
||
|
|
||
|
def test_link_in_error(self):
|
||
|
# Raise an error by trying to link two modules with the same global
|
||
|
# definition "sum".
|
||
|
dest = self.module()
|
||
|
src = self.module(asm_sum2)
|
||
|
with self.assertRaises(RuntimeError) as cm:
|
||
|
dest.link_in(src)
|
||
|
self.assertIn("symbol multiply defined", str(cm.exception))
|
||
|
|
||
|
def test_as_bitcode(self):
|
||
|
mod = self.module()
|
||
|
bc = mod.as_bitcode()
|
||
|
# Refer to http://llvm.org/docs/doxygen/html/ReaderWriter_8h_source.html#l00064 # noqa E501
|
||
|
# and http://llvm.org/docs/doxygen/html/ReaderWriter_8h_source.html#l00092 # noqa E501
|
||
|
bitcode_wrapper_magic = b'\xde\xc0\x17\x0b'
|
||
|
bitcode_magic = b'BC'
|
||
|
self.assertTrue(bc.startswith(bitcode_magic) or
|
||
|
bc.startswith(bitcode_wrapper_magic))
|
||
|
|
||
|
def test_parse_bitcode_error(self):
|
||
|
with self.assertRaises(RuntimeError) as cm:
|
||
|
llvm.parse_bitcode(b"")
|
||
|
self.assertIn("LLVM bitcode parsing error", str(cm.exception))
|
||
|
# for llvm < 9
|
||
|
if llvm.llvm_version_info[0] < 9:
|
||
|
self.assertIn("Invalid bitcode signature", str(cm.exception))
|
||
|
else:
|
||
|
self.assertIn(
|
||
|
"file too small to contain bitcode header", str(cm.exception),
|
||
|
)
|
||
|
|
||
|
def test_bitcode_roundtrip(self):
|
||
|
# create a new context to avoid struct renaming
|
||
|
context1 = llvm.create_context()
|
||
|
bc = self.module(context=context1).as_bitcode()
|
||
|
context2 = llvm.create_context()
|
||
|
mod = llvm.parse_bitcode(bc, context2)
|
||
|
self.assertEqual(mod.as_bitcode(), bc)
|
||
|
|
||
|
mod.get_function("sum")
|
||
|
mod.get_global_variable("glob")
|
||
|
|
||
|
def test_cloning(self):
|
||
|
m = self.module()
|
||
|
cloned = m.clone()
|
||
|
self.assertIsNot(cloned, m)
|
||
|
self.assertEqual(cloned.as_bitcode(), m.as_bitcode())
|
||
|
|
||
|
|
||
|
class JITTestMixin(object):
|
||
|
"""
|
||
|
Mixin for ExecutionEngine tests.
|
||
|
"""
|
||
|
|
||
|
def get_sum(self, ee, func_name="sum"):
|
||
|
ee.finalize_object()
|
||
|
cfptr = ee.get_function_address(func_name)
|
||
|
self.assertTrue(cfptr)
|
||
|
return CFUNCTYPE(c_int, c_int, c_int)(cfptr)
|
||
|
|
||
|
def test_run_code(self):
|
||
|
mod = self.module()
|
||
|
with self.jit(mod) as ee:
|
||
|
cfunc = self.get_sum(ee)
|
||
|
res = cfunc(2, -5)
|
||
|
self.assertEqual(-3, res)
|
||
|
|
||
|
def test_close(self):
|
||
|
ee = self.jit(self.module())
|
||
|
ee.close()
|
||
|
ee.close()
|
||
|
with self.assertRaises(ctypes.ArgumentError):
|
||
|
ee.finalize_object()
|
||
|
|
||
|
def test_with(self):
|
||
|
ee = self.jit(self.module())
|
||
|
with ee:
|
||
|
pass
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
with ee:
|
||
|
pass
|
||
|
with self.assertRaises(ctypes.ArgumentError):
|
||
|
ee.finalize_object()
|
||
|
|
||
|
def test_module_lifetime(self):
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
ee.close()
|
||
|
mod.close()
|
||
|
|
||
|
def test_module_lifetime2(self):
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
mod.close()
|
||
|
ee.close()
|
||
|
|
||
|
def test_add_module(self):
|
||
|
ee = self.jit(self.module())
|
||
|
mod = self.module(asm_mul)
|
||
|
ee.add_module(mod)
|
||
|
with self.assertRaises(KeyError):
|
||
|
ee.add_module(mod)
|
||
|
self.assertFalse(mod.closed)
|
||
|
ee.close()
|
||
|
self.assertTrue(mod.closed)
|
||
|
|
||
|
def test_add_module_lifetime(self):
|
||
|
ee = self.jit(self.module())
|
||
|
mod = self.module(asm_mul)
|
||
|
ee.add_module(mod)
|
||
|
mod.close()
|
||
|
ee.close()
|
||
|
|
||
|
def test_add_module_lifetime2(self):
|
||
|
ee = self.jit(self.module())
|
||
|
mod = self.module(asm_mul)
|
||
|
ee.add_module(mod)
|
||
|
ee.close()
|
||
|
mod.close()
|
||
|
|
||
|
def test_remove_module(self):
|
||
|
ee = self.jit(self.module())
|
||
|
mod = self.module(asm_mul)
|
||
|
ee.add_module(mod)
|
||
|
ee.remove_module(mod)
|
||
|
with self.assertRaises(KeyError):
|
||
|
ee.remove_module(mod)
|
||
|
self.assertFalse(mod.closed)
|
||
|
ee.close()
|
||
|
self.assertFalse(mod.closed)
|
||
|
|
||
|
def test_target_data(self):
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
td = ee.target_data
|
||
|
# A singleton is returned
|
||
|
self.assertIs(ee.target_data, td)
|
||
|
str(td)
|
||
|
del mod, ee
|
||
|
str(td)
|
||
|
|
||
|
def test_target_data_abi_enquiries(self):
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
td = ee.target_data
|
||
|
gv_i32 = mod.get_global_variable("glob")
|
||
|
gv_i8 = mod.get_global_variable("glob_b")
|
||
|
gv_struct = mod.get_global_variable("glob_struct")
|
||
|
# A global is a pointer, it has the ABI size of a pointer
|
||
|
pointer_size = 4 if sys.maxsize < 2 ** 32 else 8
|
||
|
for g in (gv_i32, gv_i8, gv_struct):
|
||
|
self.assertEqual(td.get_abi_size(g.type), pointer_size)
|
||
|
|
||
|
self.assertEqual(td.get_pointee_abi_size(gv_i32.type), 4)
|
||
|
self.assertEqual(td.get_pointee_abi_alignment(gv_i32.type), 4)
|
||
|
|
||
|
self.assertEqual(td.get_pointee_abi_size(gv_i8.type), 1)
|
||
|
self.assertIn(td.get_pointee_abi_alignment(gv_i8.type), (1, 2, 4))
|
||
|
|
||
|
self.assertEqual(td.get_pointee_abi_size(gv_struct.type), 24)
|
||
|
self.assertIn(td.get_pointee_abi_alignment(gv_struct.type), (4, 8))
|
||
|
|
||
|
def test_object_cache_notify(self):
|
||
|
notifies = []
|
||
|
|
||
|
def notify(mod, buf):
|
||
|
notifies.append((mod, buf))
|
||
|
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
ee.set_object_cache(notify)
|
||
|
|
||
|
self.assertEqual(len(notifies), 0)
|
||
|
cfunc = self.get_sum(ee)
|
||
|
cfunc(2, -5)
|
||
|
self.assertEqual(len(notifies), 1)
|
||
|
# The right module object was found
|
||
|
self.assertIs(notifies[0][0], mod)
|
||
|
self.assertIsInstance(notifies[0][1], bytes)
|
||
|
|
||
|
notifies[:] = []
|
||
|
mod2 = self.module(asm_mul)
|
||
|
ee.add_module(mod2)
|
||
|
cfunc = self.get_sum(ee, "mul")
|
||
|
self.assertEqual(len(notifies), 1)
|
||
|
# The right module object was found
|
||
|
self.assertIs(notifies[0][0], mod2)
|
||
|
self.assertIsInstance(notifies[0][1], bytes)
|
||
|
|
||
|
def test_object_cache_getbuffer(self):
|
||
|
notifies = []
|
||
|
getbuffers = []
|
||
|
|
||
|
def notify(mod, buf):
|
||
|
notifies.append((mod, buf))
|
||
|
|
||
|
def getbuffer(mod):
|
||
|
getbuffers.append(mod)
|
||
|
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod)
|
||
|
ee.set_object_cache(notify, getbuffer)
|
||
|
|
||
|
# First return None from getbuffer(): the object is compiled normally
|
||
|
self.assertEqual(len(notifies), 0)
|
||
|
self.assertEqual(len(getbuffers), 0)
|
||
|
cfunc = self.get_sum(ee)
|
||
|
self.assertEqual(len(notifies), 1)
|
||
|
self.assertEqual(len(getbuffers), 1)
|
||
|
self.assertIs(getbuffers[0], mod)
|
||
|
sum_buffer = notifies[0][1]
|
||
|
|
||
|
# Recreate a new EE, and use getbuffer() to return the previously
|
||
|
# compiled object.
|
||
|
|
||
|
def getbuffer_successful(mod):
|
||
|
getbuffers.append(mod)
|
||
|
return sum_buffer
|
||
|
|
||
|
notifies[:] = []
|
||
|
getbuffers[:] = []
|
||
|
# Use another source module to make sure it is ignored
|
||
|
mod = self.module(asm_mul)
|
||
|
ee = self.jit(mod)
|
||
|
ee.set_object_cache(notify, getbuffer_successful)
|
||
|
|
||
|
self.assertEqual(len(notifies), 0)
|
||
|
self.assertEqual(len(getbuffers), 0)
|
||
|
cfunc = self.get_sum(ee)
|
||
|
self.assertEqual(cfunc(2, -5), -3)
|
||
|
self.assertEqual(len(notifies), 0)
|
||
|
self.assertEqual(len(getbuffers), 1)
|
||
|
|
||
|
|
||
|
class JITWithTMTestMixin(JITTestMixin):
|
||
|
|
||
|
def test_emit_assembly(self):
|
||
|
"""Test TargetMachineRef.emit_assembly()"""
|
||
|
target_machine = self.target_machine(jit=True)
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod, target_machine) # noqa F841 # Keeps pointers alive
|
||
|
raw_asm = target_machine.emit_assembly(mod)
|
||
|
self.assertIn("sum", raw_asm)
|
||
|
target_machine.set_asm_verbosity(True)
|
||
|
raw_asm_verbose = target_machine.emit_assembly(mod)
|
||
|
self.assertIn("sum", raw_asm)
|
||
|
self.assertNotEqual(raw_asm, raw_asm_verbose)
|
||
|
|
||
|
def test_emit_object(self):
|
||
|
"""Test TargetMachineRef.emit_object()"""
|
||
|
target_machine = self.target_machine(jit=True)
|
||
|
mod = self.module()
|
||
|
ee = self.jit(mod, target_machine) # noqa F841 # Keeps pointers alive
|
||
|
code_object = target_machine.emit_object(mod)
|
||
|
self.assertIsInstance(code_object, bytes)
|
||
|
if sys.platform.startswith('linux'):
|
||
|
# Sanity check
|
||
|
self.assertIn(b"ELF", code_object[:10])
|
||
|
|
||
|
|
||
|
class TestMCJit(BaseTest, JITWithTMTestMixin):
|
||
|
"""
|
||
|
Test JIT engines created with create_mcjit_compiler().
|
||
|
"""
|
||
|
|
||
|
def jit(self, mod, target_machine=None):
|
||
|
if target_machine is None:
|
||
|
target_machine = self.target_machine(jit=True)
|
||
|
return llvm.create_mcjit_compiler(mod, target_machine)
|
||
|
|
||
|
|
||
|
# There are some memory corruption issues with OrcJIT on AArch64 - see Issue
|
||
|
# #1000. Since OrcJIT is experimental, and we don't test regularly during
|
||
|
# llvmlite development on non-x86 platforms, it seems safest to skip these
|
||
|
# tests on non-x86 platforms.
|
||
|
@unittest.skipUnless(platform.machine().startswith("x86"), "x86 only")
|
||
|
class TestOrcLLJIT(BaseTest):
|
||
|
|
||
|
def jit(self, asm=asm_sum, func_name="sum", target_machine=None,
|
||
|
add_process=False, func_type=CFUNCTYPE(c_int, c_int, c_int),
|
||
|
suppress_errors=False):
|
||
|
lljit = llvm.create_lljit_compiler(target_machine,
|
||
|
use_jit_link=False,
|
||
|
suppress_errors=suppress_errors)
|
||
|
builder = llvm.JITLibraryBuilder()
|
||
|
if add_process:
|
||
|
builder.add_current_process()
|
||
|
rt = builder\
|
||
|
.add_ir(asm.format(triple=llvm.get_default_triple()))\
|
||
|
.export_symbol(func_name)\
|
||
|
.link(lljit, func_name)
|
||
|
cfptr = rt[func_name]
|
||
|
self.assertTrue(cfptr)
|
||
|
self.assertEqual(func_name, rt.name)
|
||
|
return lljit, rt, func_type(cfptr)
|
||
|
|
||
|
# From test_dylib_symbols
|
||
|
def test_define_symbol(self):
|
||
|
lljit = llvm.create_lljit_compiler()
|
||
|
rt = llvm.JITLibraryBuilder().import_symbol("__xyzzy", 1234)\
|
||
|
.export_symbol("__xyzzy").link(lljit, "foo")
|
||
|
self.assertEqual(rt["__xyzzy"], 1234)
|
||
|
|
||
|
def test_lookup_undefined_symbol_fails(self):
|
||
|
lljit = llvm.create_lljit_compiler()
|
||
|
with self.assertRaisesRegex(RuntimeError, 'No such library'):
|
||
|
lljit.lookup("foo", "__foobar")
|
||
|
rt = llvm.JITLibraryBuilder().import_symbol("__xyzzy", 1234)\
|
||
|
.export_symbol("__xyzzy").link(lljit, "foo")
|
||
|
self.assertNotEqual(rt["__xyzzy"], 0)
|
||
|
with self.assertRaisesRegex(RuntimeError,
|
||
|
'Symbols not found.*__foobar'):
|
||
|
lljit.lookup("foo", "__foobar")
|
||
|
|
||
|
def test_jit_link(self):
|
||
|
if sys.platform == "win32":
|
||
|
with self.assertRaisesRegex(RuntimeError,
|
||
|
'JITLink .* Windows'):
|
||
|
llvm.create_lljit_compiler(use_jit_link=True)
|
||
|
else:
|
||
|
self.assertIsNotNone(llvm.create_lljit_compiler(use_jit_link=True))
|
||
|
|
||
|
def test_run_code(self):
|
||
|
(lljit, rt, cfunc) = self.jit()
|
||
|
with lljit:
|
||
|
res = cfunc(2, -5)
|
||
|
self.assertEqual(-3, res)
|
||
|
|
||
|
def test_close(self):
|
||
|
(lljit, rt, cfunc) = self.jit()
|
||
|
lljit.close()
|
||
|
lljit.close()
|
||
|
with self.assertRaises(AssertionError):
|
||
|
lljit.lookup("foo", "fn")
|
||
|
|
||
|
def test_with(self):
|
||
|
(lljit, rt, cfunc) = self.jit()
|
||
|
with lljit:
|
||
|
pass
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
with lljit:
|
||
|
pass
|
||
|
with self.assertRaises(AssertionError):
|
||
|
lljit.lookup("foo", "fn")
|
||
|
|
||
|
def test_add_ir_module(self):
|
||
|
(lljit, rt_sum, cfunc_sum) = self.jit()
|
||
|
rt_mul = llvm.JITLibraryBuilder() \
|
||
|
.add_ir(asm_mul.format(triple=llvm.get_default_triple())) \
|
||
|
.export_symbol("mul") \
|
||
|
.link(lljit, "mul")
|
||
|
res = CFUNCTYPE(c_int, c_int, c_int)(rt_mul["mul"])(2, -5)
|
||
|
self.assertEqual(-10, res)
|
||
|
self.assertNotEqual(lljit.lookup("sum", "sum")["sum"], 0)
|
||
|
self.assertNotEqual(lljit.lookup("mul", "mul")["mul"], 0)
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
lljit.lookup("sum", "mul")
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
lljit.lookup("mul", "sum")
|
||
|
|
||
|
def test_remove_module(self):
|
||
|
(lljit, rt_sum, _) = self.jit()
|
||
|
del rt_sum
|
||
|
gc.collect()
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
lljit.lookup("sum", "sum")
|
||
|
lljit.close()
|
||
|
|
||
|
def test_lib_depends(self):
|
||
|
(lljit, rt_sum, cfunc_sum) = self.jit()
|
||
|
rt_mul = llvm.JITLibraryBuilder() \
|
||
|
.add_ir(asm_square_sum.format(triple=llvm.get_default_triple())) \
|
||
|
.export_symbol("square_sum") \
|
||
|
.add_jit_library("sum") \
|
||
|
.link(lljit, "square_sum")
|
||
|
res = CFUNCTYPE(c_int, c_int, c_int)(rt_mul["square_sum"])(2, -5)
|
||
|
self.assertEqual(9, res)
|
||
|
|
||
|
def test_target_data(self):
|
||
|
(lljit, rt, _) = self.jit()
|
||
|
td = lljit.target_data
|
||
|
# A singleton is returned
|
||
|
self.assertIs(lljit.target_data, td)
|
||
|
str(td)
|
||
|
del lljit
|
||
|
str(td)
|
||
|
|
||
|
def test_global_ctors_dtors(self):
|
||
|
# test issue #303
|
||
|
# (https://github.com/numba/llvmlite/issues/303)
|
||
|
shared_value = c_int32(0)
|
||
|
lljit = llvm.create_lljit_compiler()
|
||
|
builder = llvm.JITLibraryBuilder()
|
||
|
rt = builder \
|
||
|
.add_ir(asm_ext_ctors.format(triple=llvm.get_default_triple())) \
|
||
|
.import_symbol("A", ctypes.addressof(shared_value)) \
|
||
|
.export_symbol("foo") \
|
||
|
.link(lljit, "foo")
|
||
|
foo = rt["foo"]
|
||
|
self.assertTrue(foo)
|
||
|
self.assertEqual(CFUNCTYPE(c_int)(foo)(), 12)
|
||
|
del rt
|
||
|
self.assertNotEqual(shared_value.value, 20)
|
||
|
|
||
|
def test_lookup_current_process_symbol_fails(self):
|
||
|
# An attempt to lookup a symbol in the current process (Py_GetVersion,
|
||
|
# in this case) should fail with an appropriate error if we have not
|
||
|
# enabled searching the current process for symbols.
|
||
|
msg = 'Failed to materialize symbols:.*getversion'
|
||
|
with self.assertRaisesRegex(RuntimeError, msg):
|
||
|
self.jit(asm_getversion, "getversion", suppress_errors=True)
|
||
|
|
||
|
def test_lookup_current_process_symbol(self):
|
||
|
self.jit(asm_getversion, "getversion", None, True)
|
||
|
|
||
|
def test_thread_safe(self):
|
||
|
lljit = llvm.create_lljit_compiler()
|
||
|
llvm_ir = asm_sum.format(triple=llvm.get_default_triple())
|
||
|
|
||
|
def compile_many(i):
|
||
|
def do_work():
|
||
|
tracking = []
|
||
|
for c in range(50):
|
||
|
tracking.append(llvm.JITLibraryBuilder()
|
||
|
.add_ir(llvm_ir)
|
||
|
.export_symbol("sum")
|
||
|
.link(lljit, f"sum_{i}_{c}"))
|
||
|
|
||
|
return do_work
|
||
|
|
||
|
ths = [threading.Thread(target=compile_many(i))
|
||
|
for i in range(os.cpu_count())]
|
||
|
for th in ths:
|
||
|
th.start()
|
||
|
for th in ths:
|
||
|
th.join()
|
||
|
|
||
|
def test_add_object_file(self):
|
||
|
target_machine = self.target_machine(jit=False)
|
||
|
mod = self.module()
|
||
|
lljit = llvm.create_lljit_compiler(target_machine)
|
||
|
rt = llvm.JITLibraryBuilder()\
|
||
|
.add_object_img(target_machine.emit_object(mod))\
|
||
|
.export_symbol("sum")\
|
||
|
.link(lljit, "sum")
|
||
|
sum = CFUNCTYPE(c_int, c_int, c_int)(rt["sum"])
|
||
|
self.assertEqual(sum(2, 3), 5)
|
||
|
|
||
|
def test_add_object_file_from_filesystem(self):
|
||
|
target_machine = self.target_machine(jit=False)
|
||
|
mod = self.module()
|
||
|
obj_bin = target_machine.emit_object(mod)
|
||
|
temp_desc, temp_path = mkstemp()
|
||
|
|
||
|
try:
|
||
|
with os.fdopen(temp_desc, "wb") as f:
|
||
|
f.write(obj_bin)
|
||
|
lljit = llvm.create_lljit_compiler(target_machine)
|
||
|
rt = llvm.JITLibraryBuilder() \
|
||
|
.add_object_file(temp_path) \
|
||
|
.export_symbol("sum") \
|
||
|
.link(lljit, "sum")
|
||
|
sum = CFUNCTYPE(c_int, c_int, c_int)(rt["sum"])
|
||
|
self.assertEqual(sum(2, 3), 5)
|
||
|
finally:
|
||
|
os.unlink(temp_path)
|
||
|
|
||
|
|
||
|
class TestValueRef(BaseTest):
|
||
|
|
||
|
def test_str(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
self.assertEqual(str(glob), "@glob = global i32 0")
|
||
|
|
||
|
def test_name(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
self.assertEqual(glob.name, "glob")
|
||
|
glob.name = "foobar"
|
||
|
self.assertEqual(glob.name, "foobar")
|
||
|
|
||
|
def test_linkage(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
linkage = glob.linkage
|
||
|
self.assertIsInstance(glob.linkage, llvm.Linkage)
|
||
|
glob.linkage = linkage
|
||
|
self.assertEqual(glob.linkage, linkage)
|
||
|
for linkage in ("internal", "external"):
|
||
|
glob.linkage = linkage
|
||
|
self.assertIsInstance(glob.linkage, llvm.Linkage)
|
||
|
self.assertEqual(glob.linkage.name, linkage)
|
||
|
|
||
|
def test_visibility(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
visibility = glob.visibility
|
||
|
self.assertIsInstance(glob.visibility, llvm.Visibility)
|
||
|
glob.visibility = visibility
|
||
|
self.assertEqual(glob.visibility, visibility)
|
||
|
for visibility in ("hidden", "protected", "default"):
|
||
|
glob.visibility = visibility
|
||
|
self.assertIsInstance(glob.visibility, llvm.Visibility)
|
||
|
self.assertEqual(glob.visibility.name, visibility)
|
||
|
|
||
|
def test_storage_class(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
storage_class = glob.storage_class
|
||
|
self.assertIsInstance(glob.storage_class, llvm.StorageClass)
|
||
|
glob.storage_class = storage_class
|
||
|
self.assertEqual(glob.storage_class, storage_class)
|
||
|
for storage_class in ("dllimport", "dllexport", "default"):
|
||
|
glob.storage_class = storage_class
|
||
|
self.assertIsInstance(glob.storage_class, llvm.StorageClass)
|
||
|
self.assertEqual(glob.storage_class.name, storage_class)
|
||
|
|
||
|
def test_add_function_attribute(self):
|
||
|
mod = self.module()
|
||
|
fn = mod.get_function("sum")
|
||
|
fn.add_function_attribute("nocapture")
|
||
|
with self.assertRaises(ValueError) as raises:
|
||
|
fn.add_function_attribute("zext")
|
||
|
self.assertEqual(str(raises.exception), "no such attribute 'zext'")
|
||
|
|
||
|
def test_module(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
self.assertIs(glob.module, mod)
|
||
|
|
||
|
def test_type(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
tp = glob.type
|
||
|
self.assertIsInstance(tp, llvm.TypeRef)
|
||
|
|
||
|
def test_type_name(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
tp = glob.type
|
||
|
self.assertEqual(tp.name, "")
|
||
|
st = mod.get_global_variable("glob_struct")
|
||
|
self.assertIsNotNone(re.match(r"struct\.glob_type(\.[\d]+)?",
|
||
|
st.type.element_type.name))
|
||
|
|
||
|
def test_type_printing_variable(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
tp = glob.type
|
||
|
self.assertEqual(str(tp), 'i32*')
|
||
|
|
||
|
def test_type_printing_function(self):
|
||
|
mod = self.module()
|
||
|
fn = mod.get_function("sum")
|
||
|
self.assertEqual(str(fn.type), "i32 (i32, i32)*")
|
||
|
|
||
|
def test_type_printing_struct(self):
|
||
|
mod = self.module()
|
||
|
st = mod.get_global_variable("glob_struct")
|
||
|
self.assertTrue(st.type.is_pointer)
|
||
|
self.assertIsNotNone(re.match(r'%struct\.glob_type(\.[\d]+)?\*',
|
||
|
str(st.type)))
|
||
|
self.assertIsNotNone(re.match(
|
||
|
r"%struct\.glob_type(\.[\d]+)? = type { i64, \[2 x i64\] }",
|
||
|
str(st.type.element_type)))
|
||
|
|
||
|
def test_close(self):
|
||
|
glob = self.glob()
|
||
|
glob.close()
|
||
|
glob.close()
|
||
|
|
||
|
def test_is_declaration(self):
|
||
|
defined = self.module().get_function('sum')
|
||
|
declared = self.module(asm_sum_declare).get_function('sum')
|
||
|
self.assertFalse(defined.is_declaration)
|
||
|
self.assertTrue(declared.is_declaration)
|
||
|
|
||
|
def test_module_global_variables(self):
|
||
|
mod = self.module(asm_sum)
|
||
|
gvars = list(mod.global_variables)
|
||
|
self.assertEqual(len(gvars), 4)
|
||
|
for v in gvars:
|
||
|
self.assertTrue(v.is_global)
|
||
|
|
||
|
def test_module_functions(self):
|
||
|
mod = self.module()
|
||
|
funcs = list(mod.functions)
|
||
|
self.assertEqual(len(funcs), 1)
|
||
|
func = funcs[0]
|
||
|
self.assertTrue(func.is_function)
|
||
|
self.assertEqual(func.name, 'sum')
|
||
|
|
||
|
with self.assertRaises(ValueError):
|
||
|
func.instructions
|
||
|
with self.assertRaises(ValueError):
|
||
|
func.operands
|
||
|
with self.assertRaises(ValueError):
|
||
|
func.opcode
|
||
|
|
||
|
def test_function_arguments(self):
|
||
|
mod = self.module()
|
||
|
func = mod.get_function('sum')
|
||
|
self.assertTrue(func.is_function)
|
||
|
args = list(func.arguments)
|
||
|
self.assertEqual(len(args), 2)
|
||
|
self.assertTrue(args[0].is_argument)
|
||
|
self.assertTrue(args[1].is_argument)
|
||
|
self.assertEqual(args[0].name, '.1')
|
||
|
self.assertEqual(str(args[0].type), 'i32')
|
||
|
self.assertEqual(args[1].name, '.2')
|
||
|
self.assertEqual(str(args[1].type), 'i32')
|
||
|
|
||
|
with self.assertRaises(ValueError):
|
||
|
args[0].blocks
|
||
|
with self.assertRaises(ValueError):
|
||
|
args[0].arguments
|
||
|
|
||
|
def test_function_blocks(self):
|
||
|
func = self.module().get_function('sum')
|
||
|
blocks = list(func.blocks)
|
||
|
self.assertEqual(len(blocks), 1)
|
||
|
block = blocks[0]
|
||
|
self.assertTrue(block.is_block)
|
||
|
|
||
|
def test_block_instructions(self):
|
||
|
func = self.module().get_function('sum')
|
||
|
insts = list(list(func.blocks)[0].instructions)
|
||
|
self.assertEqual(len(insts), 3)
|
||
|
self.assertTrue(insts[0].is_instruction)
|
||
|
self.assertTrue(insts[1].is_instruction)
|
||
|
self.assertTrue(insts[2].is_instruction)
|
||
|
self.assertEqual(insts[0].opcode, 'add')
|
||
|
self.assertEqual(insts[1].opcode, 'add')
|
||
|
self.assertEqual(insts[2].opcode, 'ret')
|
||
|
|
||
|
def test_instruction_operands(self):
|
||
|
func = self.module().get_function('sum')
|
||
|
add = list(list(func.blocks)[0].instructions)[0]
|
||
|
self.assertEqual(add.opcode, 'add')
|
||
|
operands = list(add.operands)
|
||
|
self.assertEqual(len(operands), 2)
|
||
|
self.assertTrue(operands[0].is_operand)
|
||
|
self.assertTrue(operands[1].is_operand)
|
||
|
self.assertEqual(operands[0].name, '.1')
|
||
|
self.assertEqual(str(operands[0].type), 'i32')
|
||
|
self.assertEqual(operands[1].name, '.2')
|
||
|
self.assertEqual(str(operands[1].type), 'i32')
|
||
|
|
||
|
def test_function_attributes(self):
|
||
|
mod = self.module(asm_attributes)
|
||
|
for func in mod.functions:
|
||
|
attrs = list(func.attributes)
|
||
|
if func.name == 'a_readonly_func':
|
||
|
self.assertEqual(attrs, [b'readonly'])
|
||
|
elif func.name == 'a_arg0_return_func':
|
||
|
self.assertEqual(attrs, [])
|
||
|
args = list(func.arguments)
|
||
|
self.assertEqual(list(args[0].attributes), [b'returned'])
|
||
|
self.assertEqual(list(args[1].attributes), [])
|
||
|
|
||
|
def test_value_kind(self):
|
||
|
mod = self.module()
|
||
|
self.assertEqual(mod.get_global_variable('glob').value_kind,
|
||
|
llvm.ValueKind.global_variable)
|
||
|
func = mod.get_function('sum')
|
||
|
self.assertEqual(func.value_kind, llvm.ValueKind.function)
|
||
|
block = list(func.blocks)[0]
|
||
|
self.assertEqual(block.value_kind, llvm.ValueKind.basic_block)
|
||
|
inst = list(block.instructions)[1]
|
||
|
self.assertEqual(inst.value_kind, llvm.ValueKind.instruction)
|
||
|
self.assertEqual(list(inst.operands)[0].value_kind,
|
||
|
llvm.ValueKind.constant_int)
|
||
|
self.assertEqual(list(inst.operands)[1].value_kind,
|
||
|
llvm.ValueKind.instruction)
|
||
|
|
||
|
iasm_func = self.module(asm_inlineasm).get_function('foo')
|
||
|
iasm_inst = list(list(iasm_func.blocks)[0].instructions)[0]
|
||
|
self.assertEqual(list(iasm_inst.operands)[0].value_kind,
|
||
|
llvm.ValueKind.inline_asm)
|
||
|
|
||
|
def test_is_constant(self):
|
||
|
mod = self.module()
|
||
|
self.assertTrue(mod.get_global_variable('glob').is_constant)
|
||
|
constant_operands = 0
|
||
|
for func in mod.functions:
|
||
|
self.assertTrue(func.is_constant)
|
||
|
for block in func.blocks:
|
||
|
self.assertFalse(block.is_constant)
|
||
|
for inst in block.instructions:
|
||
|
self.assertFalse(inst.is_constant)
|
||
|
for op in inst.operands:
|
||
|
if op.is_constant:
|
||
|
constant_operands += 1
|
||
|
|
||
|
self.assertEqual(constant_operands, 1)
|
||
|
|
||
|
def test_constant_int(self):
|
||
|
mod = self.module()
|
||
|
func = mod.get_function('sum')
|
||
|
insts = list(list(func.blocks)[0].instructions)
|
||
|
self.assertEqual(insts[1].opcode, 'add')
|
||
|
operands = list(insts[1].operands)
|
||
|
self.assertTrue(operands[0].is_constant)
|
||
|
self.assertFalse(operands[1].is_constant)
|
||
|
self.assertEqual(operands[0].get_constant_value(), 0)
|
||
|
with self.assertRaises(ValueError):
|
||
|
operands[1].get_constant_value()
|
||
|
|
||
|
mod = self.module(asm_sum3)
|
||
|
func = mod.get_function('sum')
|
||
|
insts = list(list(func.blocks)[0].instructions)
|
||
|
posint64 = list(insts[1].operands)[0]
|
||
|
negint64 = list(insts[2].operands)[0]
|
||
|
self.assertEqual(posint64.get_constant_value(), 5)
|
||
|
self.assertEqual(negint64.get_constant_value(signed_int=True), -5)
|
||
|
|
||
|
# Convert from unsigned arbitrary-precision integer to signed i64
|
||
|
as_u64 = negint64.get_constant_value(signed_int=False)
|
||
|
as_i64 = int.from_bytes(as_u64.to_bytes(8, 'little'), 'little',
|
||
|
signed=True)
|
||
|
self.assertEqual(as_i64, -5)
|
||
|
|
||
|
def test_constant_fp(self):
|
||
|
mod = self.module(asm_double_locale)
|
||
|
func = mod.get_function('foo')
|
||
|
insts = list(list(func.blocks)[0].instructions)
|
||
|
self.assertEqual(len(insts), 2)
|
||
|
self.assertEqual(insts[0].opcode, 'fadd')
|
||
|
operands = list(insts[0].operands)
|
||
|
self.assertTrue(operands[0].is_constant)
|
||
|
self.assertAlmostEqual(operands[0].get_constant_value(), 0.0)
|
||
|
self.assertTrue(operands[1].is_constant)
|
||
|
self.assertAlmostEqual(operands[1].get_constant_value(), 3.14)
|
||
|
|
||
|
mod = self.module(asm_double_inaccurate)
|
||
|
func = mod.get_function('foo')
|
||
|
inst = list(list(func.blocks)[0].instructions)[0]
|
||
|
operands = list(inst.operands)
|
||
|
with self.assertRaises(ValueError):
|
||
|
operands[0].get_constant_value()
|
||
|
self.assertAlmostEqual(operands[1].get_constant_value(round_fp=True), 0)
|
||
|
|
||
|
def test_constant_as_string(self):
|
||
|
mod = self.module(asm_null_constant)
|
||
|
func = mod.get_function('bar')
|
||
|
inst = list(list(func.blocks)[0].instructions)[0]
|
||
|
arg = list(inst.operands)[0]
|
||
|
self.assertTrue(arg.is_constant)
|
||
|
self.assertEqual(arg.get_constant_value(), 'i64* null')
|
||
|
|
||
|
def test_incoming_phi_blocks(self):
|
||
|
mod = self.module(asm_phi_blocks)
|
||
|
func = mod.get_function('foo')
|
||
|
blocks = list(func.blocks)
|
||
|
instructions = list(blocks[-1].instructions)
|
||
|
self.assertTrue(instructions[0].is_instruction)
|
||
|
self.assertEqual(instructions[0].opcode, 'phi')
|
||
|
|
||
|
incoming_blocks = list(instructions[0].incoming_blocks)
|
||
|
self.assertEqual(len(incoming_blocks), 2)
|
||
|
self.assertTrue(incoming_blocks[0].is_block)
|
||
|
self.assertTrue(incoming_blocks[1].is_block)
|
||
|
# Test reference to blocks (named or unnamed)
|
||
|
self.assertEqual(incoming_blocks[0], blocks[-1])
|
||
|
self.assertEqual(incoming_blocks[1], blocks[0])
|
||
|
|
||
|
# Test case that should fail
|
||
|
self.assertNotEqual(instructions[1].opcode, 'phi')
|
||
|
with self.assertRaises(ValueError):
|
||
|
instructions[1].incoming_blocks
|
||
|
|
||
|
|
||
|
class TestTypeRef(BaseTest):
|
||
|
|
||
|
def test_str(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
self.assertEqual(str(glob.type), "i32*")
|
||
|
glob_struct_type = mod.get_struct_type("struct.glob_type")
|
||
|
self.assertEqual(str(glob_struct_type),
|
||
|
"%struct.glob_type = type { i64, [2 x i64] }")
|
||
|
|
||
|
elements = list(glob_struct_type.elements)
|
||
|
self.assertEqual(len(elements), 2)
|
||
|
self.assertEqual(str(elements[0]), "i64")
|
||
|
self.assertEqual(str(elements[1]), "[2 x i64]")
|
||
|
|
||
|
def test_type_kind(self):
|
||
|
mod = self.module()
|
||
|
glob = mod.get_global_variable("glob")
|
||
|
self.assertEqual(glob.type.type_kind, llvm.TypeKind.pointer)
|
||
|
self.assertTrue(glob.type.is_pointer)
|
||
|
|
||
|
glob_struct = mod.get_global_variable("glob_struct")
|
||
|
self.assertEqual(glob_struct.type.type_kind, llvm.TypeKind.pointer)
|
||
|
self.assertTrue(glob_struct.type.is_pointer)
|
||
|
|
||
|
stype = next(iter(glob_struct.type.elements))
|
||
|
self.assertEqual(stype.type_kind, llvm.TypeKind.struct)
|
||
|
self.assertTrue(stype.is_struct)
|
||
|
|
||
|
stype_a, stype_b = stype.elements
|
||
|
self.assertEqual(stype_a.type_kind, llvm.TypeKind.integer)
|
||
|
self.assertEqual(stype_b.type_kind, llvm.TypeKind.array)
|
||
|
self.assertTrue(stype_b.is_array)
|
||
|
|
||
|
glob_vec_struct_type = mod.get_struct_type("struct.glob_type_vec")
|
||
|
_, vector_type = glob_vec_struct_type.elements
|
||
|
self.assertEqual(vector_type.type_kind, llvm.TypeKind.vector)
|
||
|
self.assertTrue(vector_type.is_vector)
|
||
|
|
||
|
funcptr = mod.get_function("sum").type
|
||
|
self.assertEqual(funcptr.type_kind, llvm.TypeKind.pointer)
|
||
|
functype, = funcptr.elements
|
||
|
self.assertEqual(functype.type_kind, llvm.TypeKind.function)
|
||
|
|
||
|
def test_element_count(self):
|
||
|
mod = self.module()
|
||
|
glob_struct_type = mod.get_struct_type("struct.glob_type")
|
||
|
_, array_type = glob_struct_type.elements
|
||
|
self.assertEqual(array_type.element_count, 2)
|
||
|
with self.assertRaises(ValueError):
|
||
|
glob_struct_type.element_count
|
||
|
|
||
|
def test_type_width(self):
|
||
|
mod = self.module()
|
||
|
glob_struct_type = mod.get_struct_type("struct.glob_type")
|
||
|
glob_vec_struct_type = mod.get_struct_type("struct.glob_type_vec")
|
||
|
integer_type, array_type = glob_struct_type.elements
|
||
|
_, vector_type = glob_vec_struct_type.elements
|
||
|
self.assertEqual(integer_type.type_width, 64)
|
||
|
self.assertEqual(vector_type.type_width, 64 * 2)
|
||
|
|
||
|
# Structs and arrays are not primitive types
|
||
|
self.assertEqual(glob_struct_type.type_width, 0)
|
||
|
self.assertEqual(array_type.type_width, 0)
|
||
|
|
||
|
def test_vararg_function(self):
|
||
|
# Variadic function
|
||
|
mod = self.module(asm_vararg_declare)
|
||
|
func = mod.get_function('vararg')
|
||
|
decltype = func.type.element_type
|
||
|
self.assertTrue(decltype.is_function_vararg)
|
||
|
|
||
|
mod = self.module(asm_sum_declare)
|
||
|
func = mod.get_function('sum')
|
||
|
decltype = func.type.element_type
|
||
|
self.assertFalse(decltype.is_function_vararg)
|
||
|
|
||
|
# test that the function pointer type cannot use is_function_vararg
|
||
|
self.assertTrue(func.type.is_pointer)
|
||
|
with self.assertRaises(ValueError) as raises:
|
||
|
func.type.is_function_vararg
|
||
|
self.assertIn("Type i32 (i32, i32)* is not a function",
|
||
|
str(raises.exception))
|
||
|
|
||
|
|
||
|
class TestTarget(BaseTest):
|
||
|
|
||
|
def test_from_triple(self):
|
||
|
f = llvm.Target.from_triple
|
||
|
with self.assertRaises(RuntimeError) as cm:
|
||
|
f("foobar")
|
||
|
self.assertIn("No available targets are compatible with",
|
||
|
str(cm.exception))
|
||
|
triple = llvm.get_default_triple()
|
||
|
target = f(triple)
|
||
|
self.assertEqual(target.triple, triple)
|
||
|
target.close()
|
||
|
|
||
|
def test_create_target_machine(self):
|
||
|
target = llvm.Target.from_triple(llvm.get_default_triple())
|
||
|
# With the default settings
|
||
|
target.create_target_machine('', '', 1, 'default', 'default')
|
||
|
# With the host's CPU
|
||
|
cpu = llvm.get_host_cpu_name()
|
||
|
target.create_target_machine(cpu, '', 1, 'default', 'default')
|
||
|
|
||
|
def test_name(self):
|
||
|
t = llvm.Target.from_triple(llvm.get_default_triple())
|
||
|
u = llvm.Target.from_default_triple()
|
||
|
self.assertIsInstance(t.name, str)
|
||
|
self.assertEqual(t.name, u.name)
|
||
|
|
||
|
def test_description(self):
|
||
|
t = llvm.Target.from_triple(llvm.get_default_triple())
|
||
|
u = llvm.Target.from_default_triple()
|
||
|
self.assertIsInstance(t.description, str)
|
||
|
self.assertEqual(t.description, u.description)
|
||
|
|
||
|
def test_str(self):
|
||
|
target = llvm.Target.from_triple(llvm.get_default_triple())
|
||
|
s = str(target)
|
||
|
self.assertIn(target.name, s)
|
||
|
self.assertIn(target.description, s)
|
||
|
|
||
|
|
||
|
class TestTargetData(BaseTest):
|
||
|
|
||
|
def target_data(self):
|
||
|
return llvm.create_target_data("e-m:e-i64:64-f80:128-n8:16:32:64-S128")
|
||
|
|
||
|
def test_get_abi_size(self):
|
||
|
td = self.target_data()
|
||
|
glob = self.glob()
|
||
|
self.assertEqual(td.get_abi_size(glob.type), 8)
|
||
|
|
||
|
def test_get_pointee_abi_size(self):
|
||
|
td = self.target_data()
|
||
|
|
||
|
glob = self.glob()
|
||
|
self.assertEqual(td.get_pointee_abi_size(glob.type), 4)
|
||
|
|
||
|
glob = self.glob("glob_struct")
|
||
|
self.assertEqual(td.get_pointee_abi_size(glob.type), 24)
|
||
|
|
||
|
def test_get_struct_element_offset(self):
|
||
|
td = self.target_data()
|
||
|
glob = self.glob("glob_struct")
|
||
|
|
||
|
with self.assertRaises(ValueError):
|
||
|
td.get_element_offset(glob.type, 0)
|
||
|
|
||
|
struct_type = glob.type.element_type
|
||
|
self.assertEqual(td.get_element_offset(struct_type, 0), 0)
|
||
|
self.assertEqual(td.get_element_offset(struct_type, 1), 8)
|
||
|
|
||
|
|
||
|
class TestTargetMachine(BaseTest):
|
||
|
|
||
|
def test_add_analysis_passes(self):
|
||
|
tm = self.target_machine(jit=False)
|
||
|
pm = llvm.create_module_pass_manager()
|
||
|
tm.add_analysis_passes(pm)
|
||
|
|
||
|
def test_target_data_from_tm(self):
|
||
|
tm = self.target_machine(jit=False)
|
||
|
td = tm.target_data
|
||
|
mod = self.module()
|
||
|
gv_i32 = mod.get_global_variable("glob")
|
||
|
# A global is a pointer, it has the ABI size of a pointer
|
||
|
pointer_size = 4 if sys.maxsize < 2 ** 32 else 8
|
||
|
self.assertEqual(td.get_abi_size(gv_i32.type), pointer_size)
|
||
|
|
||
|
|
||
|
class TestPassManagerBuilder(BaseTest):
|
||
|
|
||
|
def pmb(self):
|
||
|
return llvm.PassManagerBuilder()
|
||
|
|
||
|
def test_old_api(self):
|
||
|
# Test the create_pass_manager_builder() factory function
|
||
|
pmb = llvm.create_pass_manager_builder()
|
||
|
pmb.inlining_threshold = 2
|
||
|
pmb.opt_level = 3
|
||
|
|
||
|
def test_close(self):
|
||
|
pmb = self.pmb()
|
||
|
pmb.close()
|
||
|
pmb.close()
|
||
|
|
||
|
def test_opt_level(self):
|
||
|
pmb = self.pmb()
|
||
|
self.assertIsInstance(pmb.opt_level, int)
|
||
|
for i in range(4):
|
||
|
pmb.opt_level = i
|
||
|
self.assertEqual(pmb.opt_level, i)
|
||
|
|
||
|
def test_size_level(self):
|
||
|
pmb = self.pmb()
|
||
|
self.assertIsInstance(pmb.size_level, int)
|
||
|
for i in range(4):
|
||
|
pmb.size_level = i
|
||
|
self.assertEqual(pmb.size_level, i)
|
||
|
|
||
|
def test_inlining_threshold(self):
|
||
|
pmb = self.pmb()
|
||
|
with self.assertRaises(NotImplementedError):
|
||
|
pmb.inlining_threshold
|
||
|
for i in (25, 80, 350):
|
||
|
pmb.inlining_threshold = i
|
||
|
|
||
|
def test_disable_unroll_loops(self):
|
||
|
pmb = self.pmb()
|
||
|
self.assertIsInstance(pmb.disable_unroll_loops, bool)
|
||
|
for b in (True, False):
|
||
|
pmb.disable_unroll_loops = b
|
||
|
self.assertEqual(pmb.disable_unroll_loops, b)
|
||
|
|
||
|
def test_loop_vectorize(self):
|
||
|
pmb = self.pmb()
|
||
|
self.assertIsInstance(pmb.loop_vectorize, bool)
|
||
|
for b in (True, False):
|
||
|
pmb.loop_vectorize = b
|
||
|
self.assertEqual(pmb.loop_vectorize, b)
|
||
|
|
||
|
def test_slp_vectorize(self):
|
||
|
pmb = self.pmb()
|
||
|
self.assertIsInstance(pmb.slp_vectorize, bool)
|
||
|
for b in (True, False):
|
||
|
pmb.slp_vectorize = b
|
||
|
self.assertEqual(pmb.slp_vectorize, b)
|
||
|
|
||
|
def test_populate_module_pass_manager(self):
|
||
|
pmb = self.pmb()
|
||
|
pm = llvm.create_module_pass_manager()
|
||
|
pmb.populate(pm)
|
||
|
pmb.close()
|
||
|
pm.close()
|
||
|
|
||
|
def test_populate_function_pass_manager(self):
|
||
|
mod = self.module()
|
||
|
pmb = self.pmb()
|
||
|
pm = llvm.create_function_pass_manager(mod)
|
||
|
pmb.populate(pm)
|
||
|
pmb.close()
|
||
|
pm.close()
|
||
|
|
||
|
|
||
|
class PassManagerTestMixin(object):
|
||
|
|
||
|
def pmb(self):
|
||
|
pmb = llvm.create_pass_manager_builder()
|
||
|
pmb.opt_level = 2
|
||
|
pmb.inlining_threshold = 300
|
||
|
return pmb
|
||
|
|
||
|
def test_close(self):
|
||
|
pm = self.pm()
|
||
|
pm.close()
|
||
|
pm.close()
|
||
|
|
||
|
|
||
|
class TestModulePassManager(BaseTest, PassManagerTestMixin):
|
||
|
|
||
|
def pm(self):
|
||
|
return llvm.create_module_pass_manager()
|
||
|
|
||
|
def test_run(self):
|
||
|
pm = self.pm()
|
||
|
self.pmb().populate(pm)
|
||
|
mod = self.module()
|
||
|
orig_asm = str(mod)
|
||
|
pm.run(mod)
|
||
|
opt_asm = str(mod)
|
||
|
# Quick check that optimizations were run, should get:
|
||
|
# define i32 @sum(i32 %.1, i32 %.2) local_unnamed_addr #0 {
|
||
|
# %.X = add i32 %.2, %.1
|
||
|
# ret i32 %.X
|
||
|
# }
|
||
|
# where X in %.X is 3 or 4
|
||
|
opt_asm_split = opt_asm.splitlines()
|
||
|
for idx, l in enumerate(opt_asm_split):
|
||
|
if l.strip().startswith('ret i32'):
|
||
|
toks = {'%.3', '%.4'}
|
||
|
for t in toks:
|
||
|
if t in l:
|
||
|
break
|
||
|
else:
|
||
|
raise RuntimeError("expected tokens not found")
|
||
|
othertoken = (toks ^ {t}).pop()
|
||
|
|
||
|
self.assertIn("%.3", orig_asm)
|
||
|
self.assertNotIn(othertoken, opt_asm)
|
||
|
break
|
||
|
else:
|
||
|
raise RuntimeError("expected IR not found")
|
||
|
|
||
|
def test_run_with_remarks_successful_inline(self):
|
||
|
pm = self.pm()
|
||
|
pm.add_function_inlining_pass(70)
|
||
|
self.pmb().populate(pm)
|
||
|
mod = self.module(asm_inlineasm2)
|
||
|
(status, remarks) = pm.run_with_remarks(mod)
|
||
|
self.assertTrue(status)
|
||
|
# Inlining has happened? The remark will tell us.
|
||
|
self.assertIn("Passed", remarks)
|
||
|
self.assertIn("inlineme", remarks)
|
||
|
|
||
|
def test_run_with_remarks_failed_inline(self):
|
||
|
pm = self.pm()
|
||
|
pm.add_function_inlining_pass(0)
|
||
|
self.pmb().populate(pm)
|
||
|
mod = self.module(asm_inlineasm3)
|
||
|
(status, remarks) = pm.run_with_remarks(mod)
|
||
|
self.assertTrue(status)
|
||
|
|
||
|
# Inlining has not happened? The remark will tell us.
|
||
|
self.assertIn("Missed", remarks)
|
||
|
self.assertIn("inlineme", remarks)
|
||
|
self.assertIn("noinline function attribute", remarks)
|
||
|
|
||
|
def test_run_with_remarks_inline_filter_out(self):
|
||
|
pm = self.pm()
|
||
|
pm.add_function_inlining_pass(70)
|
||
|
self.pmb().populate(pm)
|
||
|
mod = self.module(asm_inlineasm2)
|
||
|
(status, remarks) = pm.run_with_remarks(mod, remarks_filter="nothing")
|
||
|
self.assertTrue(status)
|
||
|
self.assertEqual("", remarks)
|
||
|
|
||
|
def test_run_with_remarks_inline_filter_in(self):
|
||
|
pm = self.pm()
|
||
|
pm.add_function_inlining_pass(70)
|
||
|
self.pmb().populate(pm)
|
||
|
mod = self.module(asm_inlineasm2)
|
||
|
(status, remarks) = pm.run_with_remarks(mod, remarks_filter="inlin.*")
|
||
|
self.assertTrue(status)
|
||
|
self.assertIn("Passed", remarks)
|
||
|
self.assertIn("inlineme", remarks)
|
||
|
|
||
|
|
||
|
class TestFunctionPassManager(BaseTest, PassManagerTestMixin):
|
||
|
|
||
|
def pm(self, mod=None):
|
||
|
mod = mod or self.module()
|
||
|
return llvm.create_function_pass_manager(mod)
|
||
|
|
||
|
def test_initfini(self):
|
||
|
pm = self.pm()
|
||
|
pm.initialize()
|
||
|
pm.finalize()
|
||
|
|
||
|
def test_run(self):
|
||
|
mod = self.module()
|
||
|
fn = mod.get_function("sum")
|
||
|
pm = self.pm(mod)
|
||
|
self.pmb().populate(pm)
|
||
|
mod.close()
|
||
|
orig_asm = str(fn)
|
||
|
pm.initialize()
|
||
|
pm.run(fn)
|
||
|
pm.finalize()
|
||
|
opt_asm = str(fn)
|
||
|
# Quick check that optimizations were run
|
||
|
self.assertIn("%.4", orig_asm)
|
||
|
self.assertNotIn("%.4", opt_asm)
|
||
|
|
||
|
def test_run_with_remarks(self):
|
||
|
mod = self.module(licm_asm)
|
||
|
fn = mod.get_function("licm")
|
||
|
pm = self.pm(mod)
|
||
|
pm.add_licm_pass()
|
||
|
self.pmb().populate(pm)
|
||
|
mod.close()
|
||
|
|
||
|
pm.initialize()
|
||
|
(ok, remarks) = pm.run_with_remarks(fn)
|
||
|
pm.finalize()
|
||
|
self.assertTrue(ok)
|
||
|
self.assertIn("Passed", remarks)
|
||
|
self.assertIn("licm", remarks)
|
||
|
|
||
|
def test_run_with_remarks_filter_out(self):
|
||
|
mod = self.module(licm_asm)
|
||
|
fn = mod.get_function("licm")
|
||
|
pm = self.pm(mod)
|
||
|
pm.add_licm_pass()
|
||
|
self.pmb().populate(pm)
|
||
|
mod.close()
|
||
|
|
||
|
pm.initialize()
|
||
|
(ok, remarks) = pm.run_with_remarks(fn, remarks_filter="nothing")
|
||
|
pm.finalize()
|
||
|
self.assertTrue(ok)
|
||
|
self.assertEqual("", remarks)
|
||
|
|
||
|
def test_run_with_remarks_filter_in(self):
|
||
|
mod = self.module(licm_asm)
|
||
|
fn = mod.get_function("licm")
|
||
|
pm = self.pm(mod)
|
||
|
pm.add_licm_pass()
|
||
|
self.pmb().populate(pm)
|
||
|
mod.close()
|
||
|
|
||
|
pm.initialize()
|
||
|
(ok, remarks) = pm.run_with_remarks(fn, remarks_filter="licm")
|
||
|
pm.finalize()
|
||
|
self.assertTrue(ok)
|
||
|
self.assertIn("Passed", remarks)
|
||
|
self.assertIn("licm", remarks)
|
||
|
|
||
|
|
||
|
class TestPasses(BaseTest, PassManagerTestMixin):
|
||
|
|
||
|
def pm(self):
|
||
|
return llvm.create_module_pass_manager()
|
||
|
|
||
|
def test_populate(self):
|
||
|
pm = self.pm()
|
||
|
pm.add_target_library_info("") # unspecified target triple
|
||
|
pm.add_constant_merge_pass()
|
||
|
pm.add_dead_arg_elimination_pass()
|
||
|
pm.add_function_attrs_pass()
|
||
|
pm.add_function_inlining_pass(225)
|
||
|
pm.add_global_dce_pass()
|
||
|
pm.add_global_optimizer_pass()
|
||
|
pm.add_ipsccp_pass()
|
||
|
pm.add_dead_code_elimination_pass()
|
||
|
pm.add_cfg_simplification_pass()
|
||
|
pm.add_gvn_pass()
|
||
|
pm.add_instruction_combining_pass()
|
||
|
pm.add_licm_pass()
|
||
|
pm.add_sccp_pass()
|
||
|
pm.add_sroa_pass()
|
||
|
pm.add_type_based_alias_analysis_pass()
|
||
|
pm.add_basic_alias_analysis_pass()
|
||
|
pm.add_loop_rotate_pass()
|
||
|
pm.add_region_info_pass()
|
||
|
pm.add_scalar_evolution_aa_pass()
|
||
|
pm.add_aggressive_dead_code_elimination_pass()
|
||
|
pm.add_aa_eval_pass()
|
||
|
pm.add_always_inliner_pass()
|
||
|
pm.add_arg_promotion_pass(42)
|
||
|
pm.add_break_critical_edges_pass()
|
||
|
pm.add_dead_store_elimination_pass()
|
||
|
pm.add_reverse_post_order_function_attrs_pass()
|
||
|
pm.add_aggressive_instruction_combining_pass()
|
||
|
pm.add_internalize_pass()
|
||
|
pm.add_jump_threading_pass(7)
|
||
|
pm.add_lcssa_pass()
|
||
|
pm.add_loop_deletion_pass()
|
||
|
pm.add_loop_extractor_pass()
|
||
|
pm.add_single_loop_extractor_pass()
|
||
|
pm.add_loop_strength_reduce_pass()
|
||
|
pm.add_loop_simplification_pass()
|
||
|
pm.add_loop_unroll_pass()
|
||
|
pm.add_loop_unroll_and_jam_pass()
|
||
|
pm.add_loop_unswitch_pass()
|
||
|
pm.add_lower_atomic_pass()
|
||
|
pm.add_lower_invoke_pass()
|
||
|
pm.add_lower_switch_pass()
|
||
|
pm.add_memcpy_optimization_pass()
|
||
|
pm.add_merge_functions_pass()
|
||
|
pm.add_merge_returns_pass()
|
||
|
pm.add_partial_inlining_pass()
|
||
|
pm.add_prune_exception_handling_pass()
|
||
|
pm.add_reassociate_expressions_pass()
|
||
|
pm.add_demote_register_to_memory_pass()
|
||
|
pm.add_sink_pass()
|
||
|
pm.add_strip_symbols_pass()
|
||
|
pm.add_strip_dead_debug_info_pass()
|
||
|
pm.add_strip_dead_prototypes_pass()
|
||
|
pm.add_strip_debug_declare_pass()
|
||
|
pm.add_strip_nondebug_symbols_pass()
|
||
|
pm.add_tail_call_elimination_pass()
|
||
|
pm.add_basic_aa_pass()
|
||
|
pm.add_dependence_analysis_pass()
|
||
|
pm.add_dot_call_graph_pass()
|
||
|
pm.add_dot_cfg_printer_pass()
|
||
|
pm.add_dot_dom_printer_pass()
|
||
|
pm.add_dot_postdom_printer_pass()
|
||
|
pm.add_globals_mod_ref_aa_pass()
|
||
|
pm.add_iv_users_pass()
|
||
|
pm.add_lazy_value_info_pass()
|
||
|
pm.add_lint_pass()
|
||
|
pm.add_module_debug_info_pass()
|
||
|
pm.add_refprune_pass()
|
||
|
pm.add_instruction_namer_pass()
|
||
|
|
||
|
@unittest.skipUnless(platform.machine().startswith("x86"), "x86 only")
|
||
|
def test_target_library_info_behavior(self):
|
||
|
"""Test a specific situation that demonstrate TLI is affecting
|
||
|
optimization. See https://github.com/numba/numba/issues/8898.
|
||
|
"""
|
||
|
def run(use_tli):
|
||
|
mod = llvm.parse_assembly(asm_tli_exp2)
|
||
|
target = llvm.Target.from_triple(mod.triple)
|
||
|
tm = target.create_target_machine()
|
||
|
pm = llvm.ModulePassManager()
|
||
|
tm.add_analysis_passes(pm)
|
||
|
if use_tli:
|
||
|
pm.add_target_library_info(mod.triple)
|
||
|
pm.add_instruction_combining_pass()
|
||
|
pm.run(mod)
|
||
|
return mod
|
||
|
|
||
|
# Run with TLI should suppress transformation of exp2 -> ldexpf
|
||
|
mod = run(use_tli=True)
|
||
|
self.assertIn("call float @llvm.exp2.f32", str(mod))
|
||
|
|
||
|
# Run without TLI will enable the transformation
|
||
|
mod = run(use_tli=False)
|
||
|
self.assertNotIn("call float @llvm.exp2.f32", str(mod))
|
||
|
self.assertIn("call float @ldexpf", str(mod))
|
||
|
|
||
|
def test_instruction_namer_pass(self):
|
||
|
asm = asm_inlineasm3.format(triple=llvm.get_default_triple())
|
||
|
mod = llvm.parse_assembly(asm)
|
||
|
|
||
|
# Run instnamer pass
|
||
|
pm = llvm.ModulePassManager()
|
||
|
pm.add_instruction_namer_pass()
|
||
|
pm.run(mod)
|
||
|
|
||
|
# Test that unnamed instructions are now named
|
||
|
func = mod.get_function('foo')
|
||
|
first_block = next(func.blocks)
|
||
|
instructions = list(first_block.instructions)
|
||
|
self.assertEqual(instructions[0].name, 'i')
|
||
|
self.assertEqual(instructions[1].name, 'i2')
|
||
|
|
||
|
|
||
|
class TestDylib(BaseTest):
|
||
|
|
||
|
def test_bad_library(self):
|
||
|
with self.assertRaises(RuntimeError):
|
||
|
llvm.load_library_permanently("zzzasdkf;jasd;l")
|
||
|
|
||
|
@unittest.skipUnless(platform.system() in ["Linux"],
|
||
|
"test only works on Linux")
|
||
|
def test_libm(self):
|
||
|
libm = find_library("m")
|
||
|
llvm.load_library_permanently(libm)
|
||
|
|
||
|
|
||
|
class TestAnalysis(BaseTest):
|
||
|
def build_ir_module(self):
|
||
|
m = ir.Module()
|
||
|
ft = ir.FunctionType(ir.IntType(32), [ir.IntType(32), ir.IntType(32)])
|
||
|
fn = ir.Function(m, ft, "foo")
|
||
|
bd = ir.IRBuilder(fn.append_basic_block())
|
||
|
x, y = fn.args
|
||
|
z = bd.add(x, y)
|
||
|
bd.ret(z)
|
||
|
return m
|
||
|
|
||
|
def test_get_function_cfg_on_ir(self):
|
||
|
mod = self.build_ir_module()
|
||
|
foo = mod.get_global('foo')
|
||
|
dot_showing_inst = llvm.get_function_cfg(foo)
|
||
|
dot_without_inst = llvm.get_function_cfg(foo, show_inst=False)
|
||
|
inst = "%.5 = add i32 %.1, %.2"
|
||
|
self.assertIn(inst, dot_showing_inst)
|
||
|
self.assertNotIn(inst, dot_without_inst)
|
||
|
|
||
|
def test_function_cfg_on_llvm_value(self):
|
||
|
defined = self.module().get_function('sum')
|
||
|
dot_showing_inst = llvm.get_function_cfg(defined, show_inst=True)
|
||
|
dot_without_inst = llvm.get_function_cfg(defined, show_inst=False)
|
||
|
# Check "digraph"
|
||
|
prefix = 'digraph'
|
||
|
self.assertIn(prefix, dot_showing_inst)
|
||
|
self.assertIn(prefix, dot_without_inst)
|
||
|
# Check function name
|
||
|
fname = "CFG for 'sum' function"
|
||
|
self.assertIn(fname, dot_showing_inst)
|
||
|
self.assertIn(fname, dot_without_inst)
|
||
|
# Check instruction
|
||
|
inst = "%.3 = add i32 %.1, %.2"
|
||
|
self.assertIn(inst, dot_showing_inst)
|
||
|
self.assertNotIn(inst, dot_without_inst)
|
||
|
|
||
|
|
||
|
class TestTypeParsing(BaseTest):
|
||
|
@contextmanager
|
||
|
def check_parsing(self):
|
||
|
mod = ir.Module()
|
||
|
# Yield to caller and provide the module for adding
|
||
|
# new GV.
|
||
|
yield mod
|
||
|
# Caller yield back and continue with testing
|
||
|
asm = str(mod)
|
||
|
llvm.parse_assembly(asm)
|
||
|
|
||
|
def test_literal_struct(self):
|
||
|
# Natural layout
|
||
|
with self.check_parsing() as mod:
|
||
|
typ = ir.LiteralStructType([ir.IntType(32)])
|
||
|
gv = ir.GlobalVariable(mod, typ, "foo")
|
||
|
# Also test constant text repr
|
||
|
gv.initializer = ir.Constant(typ, [1])
|
||
|
|
||
|
# Packed layout
|
||
|
with self.check_parsing() as mod:
|
||
|
typ = ir.LiteralStructType([ir.IntType(32)],
|
||
|
packed=True)
|
||
|
gv = ir.GlobalVariable(mod, typ, "foo")
|
||
|
# Also test constant text repr
|
||
|
gv.initializer = ir.Constant(typ, [1])
|
||
|
|
||
|
|
||
|
class TestGlobalConstructors(TestMCJit):
|
||
|
def test_global_ctors_dtors(self):
|
||
|
# test issue #303
|
||
|
# (https://github.com/numba/llvmlite/issues/303)
|
||
|
mod = self.module(asm_global_ctors)
|
||
|
ee = self.jit(mod)
|
||
|
ee.finalize_object()
|
||
|
|
||
|
ee.run_static_constructors()
|
||
|
|
||
|
# global variable should have been initialized
|
||
|
ptr_addr = ee.get_global_value_address("A")
|
||
|
ptr_t = ctypes.POINTER(ctypes.c_int32)
|
||
|
ptr = ctypes.cast(ptr_addr, ptr_t)
|
||
|
self.assertEqual(ptr.contents.value, 10)
|
||
|
|
||
|
foo_addr = ee.get_function_address("foo")
|
||
|
foo = ctypes.CFUNCTYPE(ctypes.c_int32)(foo_addr)
|
||
|
self.assertEqual(foo(), 12)
|
||
|
|
||
|
ee.run_static_destructors()
|
||
|
|
||
|
# destructor should have run
|
||
|
self.assertEqual(ptr.contents.value, 20)
|
||
|
|
||
|
|
||
|
class TestGlobalVariables(BaseTest):
|
||
|
def check_global_variable_linkage(self, linkage, has_undef=True):
|
||
|
# This test default initializer on global variables with different
|
||
|
# linkages. Some linkages requires an initializer be present, while
|
||
|
# it is optional for others. This test uses ``parse_assembly()``
|
||
|
# to verify that we are adding an `undef` automatically if user didn't
|
||
|
# specific one for certain linkages. It is a IR syntax error if the
|
||
|
# initializer is not present for certain linkages e.g. "external".
|
||
|
mod = ir.Module()
|
||
|
typ = ir.IntType(32)
|
||
|
gv = ir.GlobalVariable(mod, typ, "foo")
|
||
|
gv.linkage = linkage
|
||
|
asm = str(mod)
|
||
|
# check if 'undef' is present
|
||
|
if has_undef:
|
||
|
self.assertIn('undef', asm)
|
||
|
else:
|
||
|
self.assertNotIn('undef', asm)
|
||
|
# parse assembly to ensure correctness
|
||
|
self.module(asm)
|
||
|
|
||
|
def test_internal_linkage(self):
|
||
|
self.check_global_variable_linkage('internal')
|
||
|
|
||
|
def test_common_linkage(self):
|
||
|
self.check_global_variable_linkage('common')
|
||
|
|
||
|
def test_external_linkage(self):
|
||
|
self.check_global_variable_linkage('external', has_undef=False)
|
||
|
|
||
|
def test_available_externally_linkage(self):
|
||
|
self.check_global_variable_linkage('available_externally')
|
||
|
|
||
|
def test_private_linkage(self):
|
||
|
self.check_global_variable_linkage('private')
|
||
|
|
||
|
def test_linkonce_linkage(self):
|
||
|
self.check_global_variable_linkage('linkonce')
|
||
|
|
||
|
def test_weak_linkage(self):
|
||
|
self.check_global_variable_linkage('weak')
|
||
|
|
||
|
def test_appending_linkage(self):
|
||
|
self.check_global_variable_linkage('appending')
|
||
|
|
||
|
def test_extern_weak_linkage(self):
|
||
|
self.check_global_variable_linkage('extern_weak', has_undef=False)
|
||
|
|
||
|
def test_linkonce_odr_linkage(self):
|
||
|
self.check_global_variable_linkage('linkonce_odr')
|
||
|
|
||
|
def test_weak_odr_linkage(self):
|
||
|
self.check_global_variable_linkage('weak_odr')
|
||
|
|
||
|
|
||
|
@unittest.skipUnless(platform.machine().startswith('x86'), "only on x86")
|
||
|
class TestInlineAsm(BaseTest):
|
||
|
def test_inlineasm(self):
|
||
|
llvm.initialize_native_asmparser()
|
||
|
m = self.module(asm=asm_inlineasm)
|
||
|
tm = self.target_machine(jit=False)
|
||
|
asm = tm.emit_assembly(m)
|
||
|
self.assertIn('nop', asm)
|
||
|
|
||
|
|
||
|
class TestObjectFile(BaseTest):
|
||
|
|
||
|
mod_asm = """
|
||
|
;ModuleID = <string>
|
||
|
target triple = "{triple}"
|
||
|
|
||
|
declare i32 @sum(i32 %.1, i32 %.2)
|
||
|
|
||
|
define i32 @sum_twice(i32 %.1, i32 %.2) {{
|
||
|
%.3 = call i32 @sum(i32 %.1, i32 %.2)
|
||
|
%.4 = call i32 @sum(i32 %.3, i32 %.3)
|
||
|
ret i32 %.4
|
||
|
}}
|
||
|
"""
|
||
|
|
||
|
def test_object_file(self):
|
||
|
target_machine = self.target_machine(jit=False)
|
||
|
mod = self.module()
|
||
|
obj_bin = target_machine.emit_object(mod)
|
||
|
obj = llvm.ObjectFileRef.from_data(obj_bin)
|
||
|
# Check that we have a text section, and that she has a name and data
|
||
|
has_text = False
|
||
|
last_address = -1
|
||
|
for s in obj.sections():
|
||
|
if s.is_text():
|
||
|
has_text = True
|
||
|
self.assertIsNotNone(s.name())
|
||
|
self.assertTrue(s.size() > 0)
|
||
|
self.assertTrue(len(s.data()) > 0)
|
||
|
self.assertIsNotNone(s.address())
|
||
|
self.assertTrue(last_address < s.address())
|
||
|
last_address = s.address()
|
||
|
break
|
||
|
self.assertTrue(has_text)
|
||
|
|
||
|
def test_add_object_file(self):
|
||
|
target_machine = self.target_machine(jit=False)
|
||
|
mod = self.module()
|
||
|
obj_bin = target_machine.emit_object(mod)
|
||
|
obj = llvm.ObjectFileRef.from_data(obj_bin)
|
||
|
|
||
|
jit = llvm.create_mcjit_compiler(self.module(self.mod_asm),
|
||
|
target_machine)
|
||
|
|
||
|
jit.add_object_file(obj)
|
||
|
|
||
|
sum_twice = CFUNCTYPE(c_int, c_int, c_int)(
|
||
|
jit.get_function_address("sum_twice"))
|
||
|
|
||
|
self.assertEqual(sum_twice(2, 3), 10)
|
||
|
|
||
|
def test_add_object_file_from_filesystem(self):
|
||
|
target_machine = self.target_machine(jit=False)
|
||
|
mod = self.module()
|
||
|
obj_bin = target_machine.emit_object(mod)
|
||
|
temp_desc, temp_path = mkstemp()
|
||
|
|
||
|
try:
|
||
|
try:
|
||
|
f = os.fdopen(temp_desc, "wb")
|
||
|
f.write(obj_bin)
|
||
|
f.flush()
|
||
|
finally:
|
||
|
f.close()
|
||
|
|
||
|
jit = llvm.create_mcjit_compiler(self.module(self.mod_asm),
|
||
|
target_machine)
|
||
|
|
||
|
jit.add_object_file(temp_path)
|
||
|
finally:
|
||
|
os.unlink(temp_path)
|
||
|
|
||
|
sum_twice = CFUNCTYPE(c_int, c_int, c_int)(
|
||
|
jit.get_function_address("sum_twice"))
|
||
|
|
||
|
self.assertEqual(sum_twice(2, 3), 10)
|
||
|
|
||
|
def test_get_section_content(self):
|
||
|
# See Issue #632 - section contents were getting truncated at null
|
||
|
# bytes.
|
||
|
elf = bytes.fromhex(issue_632_elf)
|
||
|
obj = llvm.ObjectFileRef.from_data(elf)
|
||
|
for s in obj.sections():
|
||
|
if s.is_text():
|
||
|
self.assertEqual(len(s.data()), 31)
|
||
|
self.assertEqual(s.data().hex(), issue_632_text)
|
||
|
|
||
|
|
||
|
class TestTimePasses(BaseTest):
|
||
|
def test_reporting(self):
|
||
|
mp = llvm.create_module_pass_manager()
|
||
|
|
||
|
pmb = llvm.create_pass_manager_builder()
|
||
|
pmb.opt_level = 3
|
||
|
pmb.populate(mp)
|
||
|
|
||
|
try:
|
||
|
llvm.set_time_passes(True)
|
||
|
mp.run(self.module())
|
||
|
mp.run(self.module())
|
||
|
mp.run(self.module())
|
||
|
finally:
|
||
|
report = llvm.report_and_reset_timings()
|
||
|
llvm.set_time_passes(False)
|
||
|
|
||
|
self.assertIsInstance(report, str)
|
||
|
self.assertEqual(report.count("Pass execution timing report"), 1)
|
||
|
|
||
|
def test_empty_report(self):
|
||
|
# Returns empty str if no data is collected
|
||
|
self.assertFalse(llvm.report_and_reset_timings())
|
||
|
|
||
|
|
||
|
class TestLLVMLockCallbacks(BaseTest):
|
||
|
def test_lock_callbacks(self):
|
||
|
events = []
|
||
|
|
||
|
def acq():
|
||
|
events.append('acq')
|
||
|
|
||
|
def rel():
|
||
|
events.append('rel')
|
||
|
|
||
|
# register callback
|
||
|
llvm.ffi.register_lock_callback(acq, rel)
|
||
|
|
||
|
# Check: events are initially empty
|
||
|
self.assertFalse(events)
|
||
|
# Call LLVM functions
|
||
|
llvm.create_module_pass_manager()
|
||
|
# Check: there must be at least one acq and one rel
|
||
|
self.assertIn("acq", events)
|
||
|
self.assertIn("rel", events)
|
||
|
|
||
|
# unregister callback
|
||
|
llvm.ffi.unregister_lock_callback(acq, rel)
|
||
|
|
||
|
# Check: removing non-existent callbacks will trigger a ValueError
|
||
|
with self.assertRaises(ValueError):
|
||
|
llvm.ffi.unregister_lock_callback(acq, rel)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
unittest.main()
|