use llvmkit_asmparser::parser;
fn parse_and_print(src: &str) -> String {
parser::parse_assembly_string(src, |module, _parsed| format!("{module}")).expect("parse")
}
fn parse_and_verify(src: &str) {
let verify = parser::parse_assembly_string(src, |module, _parsed| module.verify_borrowed())
.expect("parse");
verify.expect("verify");
}
#[test]
fn parses_void_function_body() {
let printed = parse_and_print("define void @f() {\nentry:\n ret void\n}\n");
assert!(printed.contains("define void @f() {\n"));
assert!(printed.contains("ret void\n"));
}
#[test]
fn parses_int_add_and_ret() {
let printed = parse_and_print(
"define i32 @add(i32 %a, i32 %b) {\nentry:\n %r = add i32 %a, %b\n ret i32 %r\n}\n",
);
assert!(printed.contains("%r = add i32 %a, %b\n"));
assert!(printed.contains("ret i32 %r\n"));
}
#[test]
fn parses_numbered_params() {
let printed = parse_and_print(
"define i32 @add(i32, i32) {\nentry:\n %r = add i32 %0, %1\n ret i32 %r\n}\n",
);
assert!(printed.contains("ret i32 %r\n"));
}
#[test]
fn parses_unreachable_terminator() {
let printed = parse_and_print("define void @abort() {\nentry:\n unreachable\n}\n");
assert!(printed.contains("\n unreachable\n"));
}
#[test]
fn parses_icmp_and_cond_br() {
let printed = parse_and_print(
"define i32 @abs(i32 %x) {\n\
entry:\n \
%is_zero = icmp eq i32 %x, 0\n \
br i1 %is_zero, label %zero_path, label %nonzero\n\
zero_path:\n \
ret i32 0\n\
nonzero:\n \
ret i32 %x\n\
}\n",
);
assert!(printed.contains("%is_zero = icmp eq i32 %x, 0\n"));
assert!(printed.contains("br i1 %is_zero, label %zero_path, label %nonzero\n"));
}
#[test]
fn parses_forward_block_reference() {
let printed = parse_and_print(
"define void @forward() {\n\
entry:\n \
br label %later\n\
later:\n \
ret void\n\
}\n",
);
assert!(printed.contains("br label %later\n"));
assert!(printed.contains("ret void\n"));
}
#[test]
fn parses_implicit_unnamed_blocks_with_shared_numbering() {
let src = "define i32 @implicit_slots(i1 %cond, i32 %x) {\n\
entry:\n \
br i1 %cond, label %0, label %1\n \
br label %1\n \
%2 = add i32 %x, 1\n \
ret i32 %2\n\
}\n";
parse_and_verify(src);
let printed = parse_and_print(src);
assert!(
printed.contains("br i1 %cond, label %0, label %1\n"),
"{printed}"
);
assert!(printed.contains("0:\n br label %1\n"), "{printed}");
assert!(
printed.contains("1:\n %2 = add i32 %x, 1\n ret i32 %2\n"),
"{printed}"
);
}
#[test]
fn parses_unnamed_non_void_callbr_result_numbering() {
let src = "declare i32 @callee()\n\
define i32 @callbr_unnamed_result() {\n\
entry:\n \
callbr i32 @callee() to label %fallthrough []\n\
fallthrough:\n \
ret i32 %0\n\
}\n";
parse_and_verify(src);
let printed = parse_and_print(src);
assert!(printed.contains("callbr i32 @callee()"), "{printed}");
assert!(printed.contains("ret i32 %0"), "{printed}");
}
#[test]
fn parses_quoted_numeric_label_as_named_block() {
let src = "define void @quoted_numeric_label() {\n\
entry:\n \
br label %\"42\"\n\
\"42\":\n \
ret void\n\
}\n";
parse_and_verify(src);
let printed = parse_and_print(src);
assert!(printed.contains("br label %\"42\""), "{printed}");
assert!(printed.contains("\"42\":"), "{printed}");
}
#[test]
fn parses_forward_numbered_block_in_definition_order() {
let src = "define i32 @forward_numbered_block_order(i1 %cond, i32 %x) {\n\
entry:\n \
br i1 %cond, label %1, label %0\n \
ret i32 %x\n\
1:\n \
%2 = add i32 %x, 1\n \
ret i32 %2\n\
}\n";
parse_and_verify(src);
let printed = parse_and_print(src);
let zero_pos = printed.find("0:\n ret i32 %x").expect("prints block 0");
let one_pos = printed
.find("1:\n %2 = add i32 %x, 1")
.expect("prints block 1");
assert!(zero_pos < one_pos, "{printed}");
}
#[test]
fn parses_sub_and_mul() {
let printed = parse_and_print(
"define i32 @poly(i32 %x) {\n\
entry:\n \
%a = sub i32 %x, 1\n \
%b = mul i32 %a, %x\n \
ret i32 %b\n\
}\n",
);
assert!(printed.contains("%a = sub i32 %x, 1\n"));
assert!(printed.contains("%b = mul i32 %a, %x\n"));
}
#[test]
fn unsupported_opcode_is_typed_error() {
let err = parser::parse_assembly_string(
"define i32 @f(i32 %a) {\nentry:\n %x = store i32 %a, ptr null\n ret i32 %a\n}\n",
|_module, _parsed| (),
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("instruction opcode supported by this parser"),
"got: {msg}"
);
}
#[test]
fn parses_div_and_rem_opcodes() {
let printed = parse_and_print(
"define i32 @divrem(i32 %a, i32 %b) {\nentry:\n \
%u = udiv i32 %a, %b\n \
%s = sdiv i32 %a, %b\n \
%ur = urem i32 %a, %b\n \
%sr = srem i32 %a, %b\n \
ret i32 %sr\n\
}\n",
);
assert!(printed.contains("%u = udiv i32 %a, %b\n"));
assert!(printed.contains("%s = sdiv i32 %a, %b\n"));
assert!(printed.contains("%ur = urem i32 %a, %b\n"));
assert!(printed.contains("%sr = srem i32 %a, %b\n"));
}
#[test]
fn parses_shift_and_bitwise_opcodes() {
let printed = parse_and_print(
"define i32 @bits(i32 %a, i32 %b) {\nentry:\n \
%s1 = shl i32 %a, 1\n \
%s2 = lshr i32 %s1, 1\n \
%s3 = ashr i32 %s2, 1\n \
%s4 = and i32 %s3, %b\n \
%s5 = or i32 %s4, %b\n \
%s6 = xor i32 %s5, %b\n \
ret i32 %s6\n\
}\n",
);
assert!(printed.contains("%s1 = shl i32 %a, 1\n"));
assert!(printed.contains("%s2 = lshr i32 %s1, 1\n"));
assert!(printed.contains("%s3 = ashr i32 %s2, 1\n"));
assert!(printed.contains("%s4 = and i32 %s3, %b\n"));
assert!(printed.contains("%s5 = or i32 %s4, %b\n"));
assert!(printed.contains("%s6 = xor i32 %s5, %b\n"));
}
#[test]
fn parses_int_casts() {
let printed = parse_and_print(
"define i64 @widen(i32 %a) {\nentry:\n \
%t = trunc i32 %a to i16\n \
%z = zext i16 %t to i32\n \
%s = sext i32 %z to i64\n \
ret i64 %s\n\
}\n",
);
assert!(printed.contains("%t = trunc i32 %a to i16\n"));
assert!(printed.contains("%z = zext i16 %t to i32\n"));
assert!(printed.contains("%s = sext i32 %z to i64\n"));
}
#[test]
fn parses_ptr_int_casts() {
let printed = parse_and_print(
"define i64 @addr(ptr %p) {\nentry:\n \
%i = ptrtoint ptr %p to i64\n \
%q = inttoptr i64 %i to ptr\n \
%j = ptrtoint ptr %q to i64\n \
ret i64 %j\n\
}\n",
);
assert!(printed.contains("%i = ptrtoint ptr %p to i64\n"));
assert!(printed.contains("%q = inttoptr i64 %i to ptr\n"));
}
#[test]
fn parses_ptrtoaddr_instruction_distinct_from_ptrtoint() {
let printed = parse_and_print(
"target datalayout = \"p1:64:64:64:32\"\n\
define i64 @test_as0(ptr %p) {\n\
%addr = ptrtoaddr ptr %p to i64\n\
ret i64 %addr\n\
}\n",
);
assert!(printed.contains("%addr = ptrtoaddr ptr %p to i64\n"));
}
#[test]
fn parses_ptrtoaddr_as1_scalar_instruction() {
let printed = parse_and_print(
"target datalayout = \"p1:64:64:64:32\"\n\
define i32 @test_as1(ptr addrspace(1) %p) {\n\
%addr = ptrtoaddr ptr addrspace(1) %p to i32\n\
ret i32 %addr\n\
}\n",
);
assert!(printed.contains("%addr = ptrtoaddr ptr addrspace(1) %p to i32\n"));
}
#[test]
fn parses_ptrtoaddr_as1_vector_instruction() {
let printed = parse_and_print(
"target datalayout = \"p1:64:64:64:32\"\n\
define <2 x i32> @test_vec_as1(<2 x ptr addrspace(1)> %p) {\n\
%addr = ptrtoaddr <2 x ptr addrspace(1)> %p to <2 x i32>\n\
ret <2 x i32> %addr\n\
}\n",
);
assert!(printed.contains("%addr = ptrtoaddr <2 x ptr addrspace(1)> %p to <2 x i32>\n"));
}
#[test]
fn parses_fp_arith_opcodes() {
let printed = parse_and_print(
"define float @fmath(float %a, float %b) {\nentry:\n \
%x = fadd float %a, %b\n \
%y = fsub float %x, %a\n \
%z = fmul float %y, %b\n \
%w = fdiv float %z, %a\n \
%r = frem float %w, %b\n \
ret float %r\n\
}\n",
);
assert!(printed.contains("%x = fadd float %a, %b\n"));
assert!(printed.contains("%y = fsub float %x, %a\n"));
assert!(printed.contains("%z = fmul float %y, %b\n"));
assert!(printed.contains("%w = fdiv float %z, %a\n"));
assert!(printed.contains("%r = frem float %w, %b\n"));
}
#[test]
fn parses_fneg_opcode() {
let printed = parse_and_print(
"define float @neg(float %a) {\nentry:\n %r = fneg float %a\n ret float %r\n}\n",
);
assert!(printed.contains("%r = fneg float %a\n"));
}
#[test]
fn parses_fcmp_opcodes() {
let printed = parse_and_print(
"define i1 @ord(float %a, float %b) {\nentry:\n \
%r = fcmp oeq float %a, %b\n \
ret i1 %r\n\
}\n",
);
assert!(printed.contains("%r = fcmp oeq float %a, %b\n"));
}
#[test]
fn parses_alloca_load_store() {
let printed = parse_and_print(
"define i32 @rw(i32 %v) {\nentry:\n \
%slot = alloca i32\n \
store i32 %v, ptr %slot\n \
%r = load i32, ptr %slot\n \
ret i32 %r\n\
}\n",
);
assert!(printed.contains("%slot = alloca i32\n"));
assert!(printed.contains("store i32 %v, ptr %slot\n"));
assert!(printed.contains("%r = load i32, ptr %slot\n"));
}
#[test]
fn parses_gep_plain_and_inbounds() {
let printed = parse_and_print(
"define ptr @walk(ptr %p, i64 %i) {\nentry:\n \
%a = getelementptr i32, ptr %p, i64 %i\n \
%b = getelementptr inbounds i32, ptr %p, i64 %i\n \
ret ptr %b\n\
}\n",
);
assert!(printed.contains("%a = getelementptr i32, ptr %p, i64 %i\n"));
assert!(printed.contains("%b = getelementptr inbounds i32, ptr %p, i64 %i\n"));
}
#[test]
fn parses_select_int_fp_ptr() {
let printed = parse_and_print(
"define void @sel(i1 %c, i32 %a, i32 %b, float %fa, float %fb, ptr %pa, ptr %pb) {\nentry:\n \
%ri = select i1 %c, i32 %a, i32 %b\n \
%rf = select i1 %c, float %fa, float %fb\n \
%rp = select i1 %c, ptr %pa, ptr %pb\n \
ret void\n\
}\n",
);
assert!(printed.contains("%ri = select i1 %c, i32 %a, i32 %b\n"));
assert!(printed.contains("%rf = select i1 %c, float %fa, float %fb\n"));
assert!(printed.contains("%rp = select i1 %c, ptr %pa, ptr %pb\n"));
}
#[test]
fn select_constant_non_i1_condition_is_rejected_before_fold() {
let err = parser::parse_assembly_string(
"define i32 @bad() {\nentry:\n %r = select i32 0, i32 5, i32 5\n ret i32 %r\n}\n",
|_module, _parsed| (),
)
.unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("i1 select condition"), "got: {msg}");
}
#[test]
fn select_constant_token_arms_are_rejected_before_fold() {
let err = parser::parse_assembly_string(
"define void @bad() {\nentry:\n %r = select i1 true, token none, token none\n ret void\n}\n",
|_module, _parsed| (),
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("select arm category supported by this session") || msg.contains("token"),
"got: {msg}"
);
}
#[test]
fn parses_fp_to_int_casts() {
let printed = parse_and_print(
"define void @to_int(float %f) {\nentry:\n \
%s = fptosi float %f to i32\n \
%u = fptoui float %f to i32\n \
ret void\n\
}\n",
);
assert!(printed.contains("%s = fptosi float %f to i32\n"));
assert!(printed.contains("%u = fptoui float %f to i32\n"));
}
#[test]
fn parses_int_to_fp_casts() {
let printed = parse_and_print(
"define void @to_fp(i32 %i) {\nentry:\n \
%s = sitofp i32 %i to float\n \
%u = uitofp i32 %i to float\n \
ret void\n\
}\n",
);
assert!(printed.contains("%s = sitofp i32 %i to float\n"));
assert!(printed.contains("%u = uitofp i32 %i to float\n"));
}
#[test]
fn parses_addrspacecast() {
let printed = parse_and_print(
"define ptr addrspace(1) @as_cast(ptr %p) {\nentry:\n \
%r = addrspacecast ptr %p to ptr addrspace(1)\n \
ret ptr addrspace(1) %r\n\
}\n",
);
assert!(printed.contains("%r = addrspacecast ptr %p to ptr addrspace(1)\n"));
}