use llvmkit_asmparser::{ll_parser::Parser, parse_error::ParseError, parser};
use llvmkit_ir::Module;
fn parse_and_render(module_name: &str, src: &[u8]) -> String {
Module::with_new(module_name, |module| {
Parser::new(src, &module)
.expect("lexer primes")
.parse_module()
.expect("parser succeeds");
format!("{module}")
})
}
fn assert_check_lines(text: &str, check_lines: &[&str]) {
let mut offset = 0;
for expected in check_lines {
let tail = &text[offset..];
let found = tail.find(expected).unwrap_or_else(|| {
panic!("missing upstream CHECK line `{expected}` after byte {offset}; got:\n{text}")
});
offset += found + expected.len();
}
}
fn assert_parse_print_parse_stable(text: &str) {
let module_name = text
.strip_prefix("; ModuleID = '")
.and_then(|tail| tail.split_once('\''))
.map_or("parser_constants_reparse", |(name, _)| name);
let reparsed = parse_and_render(module_name, text.as_bytes());
assert_eq!(reparsed, text);
}
fn assert_parse_error(src: &[u8], expected_message: &str) {
let err = Module::with_new("parser_constants_error", |module| {
Parser::new(src, &module)
.expect("lexer primes")
.parse_module()
.expect_err("fixture is rejected")
});
match err {
ParseError::Expected { expected, .. } => assert_eq!(expected, expected_message),
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn struct_constant_initializer_round_trips() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/aggregate-constant-values/struct_constant_initializer_round_trips.ll"
);
let text = parse_and_render("struct_constant_initializer_round_trips", FIXTURE);
assert_check_lines(
&text,
&["@foo", "store { i32, i32 } { i32 7, i32 9 }, ptr %x", "ret"],
);
}
#[test]
fn array_constant_initializer_round_trips() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/aggregate-constant-values/array_constant_initializer_round_trips.ll"
);
let text = parse_and_render("array_constant_initializer_round_trips", FIXTURE);
assert_check_lines(
&text,
&["@bar", "store [2 x i32] [i32 7, i32 9], ptr %x", "ret"],
);
}
#[test]
fn scalable_vector_splat_constant_round_trips() {
let text = parse_and_render(
"scalable_vector_splat_constant_round_trips",
b"@v = global <vscale x 2 x i32> splat (i32 7)\n",
);
assert_check_lines(&text, &["@v = global <vscale x 2 x i32> splat (i32 7)"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn getelementptr_constant_expr_initializer_round_trips() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/getelementptr_constant_expr_initializer_round_trips.ll"
);
let text = parse_and_render(
"getelementptr_constant_expr_initializer_round_trips",
FIXTURE,
);
assert_check_lines(
&text,
&["@ptr = global ptr getelementptr inbounds (i8, ptr @data, i64 1)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_casts_round_trip() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/ConstantExprNoFold/constant_expr_casts_round_trip.ll");
let text = parse_and_render("constant_expr_casts_round_trip", FIXTURE);
assert_check_lines(
&text,
&["@E = global ptr addrspace(1) addrspacecast (ptr @A to ptr addrspace(1))"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_fold_full_vector_gep_and_bitcast_fixture() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/ConstantExprFold/constant_expr_fold_full_vector_gep_and_bitcast_fixture.ll"
);
let text = parse_and_render(
"constant_expr_fold_full_vector_gep_and_bitcast_fixture",
FIXTURE,
);
assert_check_lines(
&text,
&[
"@A = global i64 0",
"@add = global ptr inttoptr (i64 ptrtoint (ptr @A to i64) to ptr)",
"@sub = global ptr inttoptr (i64 ptrtoint (ptr @A to i64) to ptr)",
"@xor = global ptr inttoptr (i64 ptrtoint (ptr @A to i64) to ptr)",
"@B = external global %Ty",
"@cons = weak global i32 0, align 8",
"@gep1 = global <2 x ptr> undef",
"@gep2 = global <2 x ptr> undef",
"@gep3 = global <2 x ptr> zeroinitializer",
"@gep4 = global <2 x ptr> zeroinitializer",
"@bitcast1 = global <2 x i32> splat (i32 -1)",
"@bitcast2 = global <4 x i16> splat (i16 -1)",
"define void @dummy()",
"ret void",
],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_fold_cast_fixture_matches_upstream() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/ConstantExprFoldCast/constant_expr_fold_cast_fixture_matches_upstream.ll"
);
let text = parse_and_render("constant_expr_fold_cast_fixture_matches_upstream", FIXTURE);
assert_parse_print_parse_stable(&text);
assert!(!text.contains("bitcast"), "{text}");
assert!(!text.contains("trunc"), "{text}");
assert_eq!(text.matches("addrspacecast").count(), 2, "{text}");
assert!(text.contains("@K = global ptr @J"), "{text}");
}
#[test]
fn constant_expr_fold_select_vector_fixture_matches_upstream() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/ConstantExprFoldSelect/constant_expr_fold_select_vector_fixture_matches_upstream.ll"
);
let text = parse_and_render(
"constant_expr_fold_select_vector_fixture_matches_upstream",
FIXTURE,
);
assert_parse_print_parse_stable(&text);
assert!(
text.contains("<i16 undef, i16 -2, i16 -3, i16 4>"),
"{text}"
);
assert!(!text.contains("select <4 x i1>"), "{text}");
}
#[test]
fn constant_expr_gep_round_trip() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/LLParser-parseValID/constant_expr_gep_round_trip.ll");
let text = parse_and_render("constant_expr_gep_round_trip", FIXTURE);
assert_check_lines(
&text,
&["@ptr = global ptr getelementptr (i8, ptr @data, i64 1)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_vector_gep_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/opaque-ptr/constexpr_vector_gep_round_trips.ll");
let text = parse_and_render("constant_expr_vector_gep_round_trips", FIXTURE);
assert_check_lines(
&text,
&["ret <2 x ptr> getelementptr (i16, ptr null, <2 x i32> <i32 3, i32 4>)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_flags_match_upstream_flags_fixture() {
const FIXTURE: &[u8] = include_bytes!("fixtures/upstream/flags/constant_expr_gep_flags.ll");
let text = parse_and_render("constant_expr_gep_flags", FIXTURE);
assert_check_lines(
&text,
&[
"ret ptr getelementptr nuw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr inbounds nuw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr nusw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr inbounds (i8, ptr @addr, i64 100)",
"ret ptr getelementptr nusw nuw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr inbounds nuw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr inbounds nuw (i8, ptr @addr, i64 100)",
"ret ptr getelementptr nuw inrange(-8, 16) (i8, ptr @addr, i64 100)",
],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_flags_addrspace_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/flags/constant_expr_gep_flags_addrspace.ll");
let text = parse_and_render("constant_expr_gep_flags_addrspace", FIXTURE);
assert_check_lines(
&text,
&["ret ptr addrspace(1) getelementptr nusw nuw (i8, ptr addrspace(1) @addr_as1, i64 100)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_inrange_apint_bounds_truncate_to_index_width() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/constant_expr_gep_inrange_apint_trunc.ll"
);
let text = parse_and_render("constant_expr_gep_inrange_apint_trunc", FIXTURE);
assert_check_lines(
&text,
&["ret ptr getelementptr inrange(0, 1) (i8, ptr @addr, i64 100)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_inrange_hex_apsint_bounds_round_trip() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/constant_expr_gep_inrange_hex_apsint.ll"
);
let text = parse_and_render("constant_expr_gep_inrange_hex_apsint", FIXTURE);
assert_check_lines(
&text,
&["ret ptr getelementptr inrange(0, 1) (i8, ptr @addr, i64 100)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_inrange_signed_hex_active_bits_are_preserved() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/constant_expr_gep_inrange_signed_hex_active_bits_invalid.ll"
);
assert_parse_error(FIXTURE, "expected end to be larger than start");
}
#[test]
fn blockaddress_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/LLParser-parseValID/blockaddress_round_trips.ll");
let text = parse_and_render("blockaddress_round_trips", FIXTURE);
assert_check_lines(&text, &["@addr = global ptr blockaddress(@f, %entry)"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn dso_local_equivalent_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/LLParser-parseValID/dso_local_equivalent_round_trips.ll");
let text = parse_and_render("dso_local_equivalent_round_trips", FIXTURE);
assert_check_lines(&text, &["@p = global ptr dso_local_equivalent @f"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn no_cfi_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/LLParser-parseValID/no_cfi_round_trips.ll");
let text = parse_and_render("no_cfi_round_trips", FIXTURE);
assert_check_lines(&text, &["@p = global ptr no_cfi @f"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn token_none_round_trips() {
Module::with_new("parser_constants_none", |module| {
let parsed =
parser::parse_constant_value(b"none", &module, module.token_type().as_type(), None)
.expect("token none parses");
assert_eq!(format!("{}", parsed.as_value()), "token none");
});
}
#[test]
fn ptrtoaddr_constant_expr_round_trips() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/ptrtoaddr/ptrtoaddr_constant_expr_round_trips.ll");
let text = parse_and_render("ptrtoaddr_constant_expr_round_trips", FIXTURE);
assert_check_lines(
&text,
&["@global_cast_as0 = global i64 ptrtoaddr (ptr @i_as0 to i64)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn ptrtoaddr_as1_constant_expr_round_trips() {
const FIXTURE: &[u8] = br#"target datalayout = "p1:64:64:64:32"
@i_as1 = addrspace(1) global i32 0
@global_cast_as1 = global i32 ptrtoaddr (ptr addrspace(1) @i_as1 to i32)
"#;
let text = parse_and_render("ptrtoaddr_as1_constant_expr_round_trips", FIXTURE);
assert_check_lines(
&text,
&["@global_cast_as1 = global i32 ptrtoaddr (ptr addrspace(1) @i_as1 to i32)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn unsupported_constant_expr_opcodes_are_rejected() {
for (opcode, src) in [
(
"fadd",
include_bytes!(
"fixtures/upstream/LLParser-parseValID/unsupported_constant_expr_fadd.ll"
)
.as_slice(),
),
(
"zext",
include_bytes!(
"fixtures/upstream/LLParser-parseValID/unsupported_constant_expr_zext.ll"
)
.as_slice(),
),
(
"mul",
include_bytes!(
"fixtures/upstream/LLParser-parseValID/unsupported_constant_expr_mul.ll"
)
.as_slice(),
),
(
"select",
include_bytes!(
"fixtures/upstream/LLParser-parseValID/unsupported_constant_expr_select.ll"
)
.as_slice(),
),
(
"icmp",
include_bytes!(
"fixtures/upstream/LLParser-parseValID/unsupported_constant_expr_icmp.ll"
)
.as_slice(),
),
] {
Module::with_new("parser_constants_unsupported", |module| {
let err = Parser::new(src, &module)
.expect("lexer primes")
.parse_module()
.expect_err("unsupported constexpr is rejected");
match err {
ParseError::Expected { expected, .. } => {
assert_eq!(
expected,
format!("{opcode} constexprs are no longer supported")
);
}
other => panic!("unexpected error variant: {other:?}"),
}
});
}
}
#[test]
fn constant_expr_shufflevector_rejects_non_i32_mask() {
assert_parse_error(
b"define <2 x i32> @bad() {\n ret <2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, <2 x i32> <i32 3, i32 4>, <2 x i64> <i64 0, i64 1>)\n}\n",
"invalid operands to shufflevector",
);
}
#[test]
fn constant_expr_shufflevector_rejects_out_of_range_mask() {
assert_parse_error(
b"define <2 x i32> @bad() {\n ret <2 x i32> shufflevector (<2 x i32> <i32 1, i32 2>, <2 x i32> <i32 3, i32 4>, <2 x i32> <i32 0, i32 4>)\n}\n",
"invalid operands to shufflevector",
);
}
#[test]
fn constant_expr_scalable_shufflevector_zero_mask_fixture_matches_upstream() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/vscale-round-trip/const_shufflevector.ll");
let text = parse_and_render(
"constant_expr_scalable_shufflevector_zero_mask_fixture_matches_upstream",
FIXTURE,
);
assert_check_lines(
&text,
&[
"define <vscale x 4 x i32> @const_shufflevector()",
"ret <vscale x 4 x i32> zeroinitializer",
"define <vscale x 4 x i32> @const_shufflevector_ex()",
"ret <vscale x 4 x i32> zeroinitializer",
],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_expr_gep_rejects_scalable_vector_pointee() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/constant-getelementptr-scalable_pointee.ll");
assert_parse_error(FIXTURE, "invalid base element for constant getelementptr");
}
#[test]
fn none_is_token_only() {
Module::with_new("parser_constants_none_token", |module| {
let parsed =
parser::parse_constant_value(b"none", &module, module.token_type().as_type(), None)
.expect("token none parses");
assert_eq!(format!("{}", parsed.as_value()), "token none");
let target_ty = module
.target_ext_type(
"spirv.Image",
Vec::<llvmkit_ir::Type>::new(),
Vec::<u32>::new(),
)
.as_type();
let err = parser::parse_constant_value(b"none", &module, target_ty, None)
.expect_err("target-extension none is rejected");
match err {
ParseError::Expected { expected, .. } => {
assert_eq!(expected, "invalid type for none constant")
}
other => panic!("unexpected error variant: {other:?}"),
}
});
}
#[test]
fn target_ext_zeroinitializer_requires_zero_init_property() {
Module::with_new("parser_constants_target_zero", |module| {
let zero_ty = module
.target_ext_type(
"spirv.foo",
Vec::<llvmkit_ir::Type>::new(),
Vec::<u32>::new(),
)
.as_type();
let zero = parser::parse_constant_value(b"zeroinitializer", &module, zero_ty, None)
.expect("zero-initializable target extension parses");
assert_eq!(
format!("{}", zero.as_value()),
"target(\"spirv.foo\") zeroinitializer"
);
let image_ty = module
.target_ext_type(
"spirv.Image",
Vec::<llvmkit_ir::Type>::new(),
Vec::<u32>::new(),
)
.as_type();
let err = parser::parse_constant_value(b"zeroinitializer", &module, image_ty, None)
.expect_err("non-zero-initializable target extension is rejected");
match err {
ParseError::Expected { expected, .. } => {
assert_eq!(expected, "invalid type for null constant")
}
other => panic!("unexpected error variant: {other:?}"),
}
});
}
#[test]
fn ptrauth_five_operands_round_trips() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/ptrauth_five_operands_round_trips.ll"
);
let text = parse_and_render("ptrauth_five_operands_round_trips", FIXTURE);
assert_check_lines(
&text,
&[
"@signed = global ptr ptrauth (ptr @g, i32 0, i64 1, ptr inttoptr (i64 1 to ptr), ptr @g)",
],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn ptrauth_default_operands_are_elided() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/ptrauth-const/ptrauth_default_operands_are_elided.ll");
let text = parse_and_render("ptrauth_default_operands_are_elided", FIXTURE);
assert_check_lines(&text, &["@basic = global ptr ptrauth (ptr @var, i32 0)"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn ptrauth_invalid_operands_match_upstream_diagnostics() {
for (fixture, expected) in [
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_base_pointer.ll")
.as_slice(),
"constant ptrauth base pointer must be a pointer",
),
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_key.ll").as_slice(),
"constant ptrauth key must be i32 constant",
),
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_addr_disc.ll")
.as_slice(),
"constant ptrauth address discriminator must be a pointer",
),
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_disc_expr.ll")
.as_slice(),
"constant ptrauth integer discriminator must be i64 constant",
),
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_disc_type.ll")
.as_slice(),
"constant ptrauth integer discriminator must be i64 constant",
),
(
include_bytes!("fixtures/upstream/ptrauth-const/invalid_ptrauth_deactivation.ll")
.as_slice(),
"constant ptrauth deactivation symbol must be a pointer",
),
] {
assert_parse_error(fixture, expected);
}
}
#[test]
fn forward_blockaddress_resolves_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/forward_blockaddress_resolves_later_signature.ll"
);
let text = parse_and_render("forward_blockaddress_resolves_later_signature", FIXTURE);
assert_check_lines(
&text,
&[
"@addr = global ptr blockaddress(@f, %entry)",
"define i32 @f(i32 %x)",
],
);
assert_eq!(text.matches("declare void @f()").count(), 0);
assert_parse_print_parse_stable(&text);
}
#[test]
fn nested_forward_blockaddress_resolves_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/nested_forward_blockaddress_resolves_later_signature.ll"
);
let text = parse_and_render(
"nested_forward_blockaddress_resolves_later_signature",
FIXTURE,
);
assert_check_lines(
&text,
&[
"@addrs = global [1 x ptr] [ptr blockaddress(@f, %entry)]",
"define i32 @f(i32 %x)",
],
);
assert_eq!(text.matches("declare void @f()").count(), 0);
assert_parse_print_parse_stable(&text);
}
#[test]
fn forward_dso_and_no_cfi_resolve_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/forward_dso_and_no_cfi_resolve_later_signature.ll"
);
let text = parse_and_render("forward_dso_and_no_cfi_resolve_later_signature", FIXTURE);
assert_check_lines(
&text,
&[
"@d = global ptr dso_local_equivalent @f",
"@n = global ptr no_cfi @f",
"declare i32 @f(i32 %0)",
],
);
assert_eq!(text.matches("declare void @f()").count(), 0);
assert_parse_print_parse_stable(&text);
}
#[test]
fn function_body_forward_blockaddress_resolves_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/function_body_forward_blockaddress_resolves_later_signature.ll"
);
let text = parse_and_render("function_body_forward_blockaddress", FIXTURE);
assert_check_lines(&text, &["ret ptr blockaddress(@f, %entry)"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn function_body_forward_aggregate_blockaddress_resolves_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/function_body_forward_aggregate_blockaddress_resolves_later_signature.ll"
);
let text = parse_and_render("function_body_forward_aggregate_blockaddress", FIXTURE);
assert_check_lines(
&text,
&["call void @sink([1 x ptr] [ptr blockaddress(@f, %entry)])"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn function_body_forward_numbered_blockaddress_resolves_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/function_body_forward_numbered_blockaddress_resolves_later_signature.ll"
);
let text = parse_and_render("function_body_forward_numbered_blockaddress", FIXTURE);
assert_check_lines(&text, &["ret ptr blockaddress(@0, %entry)"]);
assert_parse_print_parse_stable(&text);
}
#[test]
fn forward_blockaddress_preserves_function_address_space() {
const FIXTURE: &[u8] =
include_bytes!("fixtures/upstream/blockaddress-addrspace/return_fwddecl_good.ll");
let text = parse_and_render("forward_blockaddress_addrspace", FIXTURE);
assert_check_lines(
&text,
&["ret ptr addrspace(2) blockaddress(@fwddecl_as2, %bb)"],
);
assert_parse_print_parse_stable(&text);
}
#[test]
fn nested_forward_dso_and_no_cfi_resolve_later_signature() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/nested_forward_dso_and_no_cfi_resolve_later_signature.ll"
);
let text = parse_and_render(
"nested_forward_dso_and_no_cfi_resolve_later_signature",
FIXTURE,
);
assert_check_lines(
&text,
&[
"@d = global [1 x ptr] [ptr dso_local_equivalent @f]",
"@n = global [1 x ptr] [ptr no_cfi @f]",
"declare i32 @f(i32 %0)",
],
);
assert_eq!(text.matches("declare void @f()").count(), 0);
assert_parse_print_parse_stable(&text);
}
#[test]
fn constant_splat_vector_round_trips() {
const FIXTURE: &[u8] = include_bytes!(
"fixtures/upstream/LLParser-parseValID/constant_splat_vector_round_trips.ll"
);
let text = parse_and_render("constant_splat_vector_round_trips", FIXTURE);
assert_check_lines(&text, &["@v = global <4 x i32> splat (i32 7)"]);
assert_parse_print_parse_stable(&text);
}