diff --git a/CMakeLists.txt b/CMakeLists.txt index efe9802f2d..270fc6fd4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ set(C_HEADERS set(ZIG_STD_SRC "${CMAKE_SOURCE_DIR}/std/bootstrap.zig" "${CMAKE_SOURCE_DIR}/std/builtin.zig" + "${CMAKE_SOURCE_DIR}/std/compiler_rt.zig" "${CMAKE_SOURCE_DIR}/std/test_runner.zig" "${CMAKE_SOURCE_DIR}/std/test_runner_libc.zig" "${CMAKE_SOURCE_DIR}/std/test_runner_nolibc.zig" diff --git a/README.md b/README.md index 803d1da590..bfae0f4409 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ and `ZIG_LIBC_STATIC_LIB_DIR` should be set to (example below). ``` mkdir build cd build -cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=/usr/include -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbeginT.o)) +cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=/usr/include -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) make make install ./run_tests diff --git a/src/all_types.hpp b/src/all_types.hpp index adb9b34f95..037d34ddd9 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1038,6 +1038,8 @@ enum BuiltinFnId { BuiltinFnIdCUndef, BuiltinFnIdCompileVar, BuiltinFnIdConstEval, + BuiltinFnIdCtz, + BuiltinFnIdClz, }; struct BuiltinFnEntry { @@ -1161,6 +1163,7 @@ struct CodeGen { uint32_t error_value_count; TypeTableEntry *err_tag_type; LLVMValueRef int_overflow_fns[2][3][4]; // [0-signed,1-unsigned][0-add,1-sub,2-mul][0-8,1-16,2-32,3-64] + LLVMValueRef int_builtin_fns[2][4]; // [0-ctz,1-clz][0-8,1-16,2-32,3-64] const char **clang_argv; int clang_argv_len; diff --git a/src/analyze.cpp b/src/analyze.cpp index fc438506c4..3e57d646c0 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4304,6 +4304,29 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry return resolved_type; } + case BuiltinFnIdCtz: + case BuiltinFnIdClz: + { + AstNode *type_node = node->data.fn_call_expr.params.at(0); + TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node); + if (int_type->id == TypeTableEntryIdInvalid) { + return int_type; + } else if (int_type->id == TypeTableEntryIdInt) { + AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field; + TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node); + if (resolved_type->id == TypeTableEntryIdInvalid) { + return resolved_type; + } + + // TODO const expr eval + + return resolved_type; + } else { + add_node_error(g, type_node, + buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name))); + return g->builtin_types.entry_invalid; + } + } } zig_unreachable(); diff --git a/src/codegen.cpp b/src/codegen.cpp index bf208a527b..7cfd699ce4 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -227,6 +227,24 @@ static LLVMValueRef get_int_overflow_fn(CodeGen *g, TypeTableEntry *type_entry, return *fn; } +static LLVMValueRef get_int_builtin_fn(CodeGen *g, TypeTableEntry *int_type, BuiltinFnId fn_id) { + // [0-ctz,1-clz][0-8,1-16,2-32,3-64] + int index0 = (fn_id == BuiltinFnIdCtz) ? 0 : 1; + int index1 = bits_index(int_type->data.integral.bit_count); + LLVMValueRef *fn = &g->int_builtin_fns[index0][index1]; + if (!*fn) { + const char *fn_name = (fn_id == BuiltinFnIdCtz) ? "cttz" : "ctlz"; + Buf *llvm_name = buf_sprintf("llvm.%s.i%d", fn_name, int_type->data.integral.bit_count); + LLVMTypeRef param_types[] = { + int_type->type_ref, + LLVMInt1Type(), + }; + LLVMTypeRef fn_type = LLVMFunctionType(int_type->type_ref, param_types, 2, false); + *fn = LLVMAddFunction(g->module, buf_ptr(llvm_name), fn_type); + } + return *fn; +} + static LLVMValueRef get_handle_value(CodeGen *g, AstNode *source_node, LLVMValueRef ptr, TypeTableEntry *type) { if (handle_is_ptr(type)) { return ptr; @@ -249,6 +267,22 @@ static LLVMValueRef gen_builtin_fn_call_expr(CodeGen *g, AstNode *node) { case BuiltinFnIdCDefine: case BuiltinFnIdCUndef: zig_unreachable(); + case BuiltinFnIdCtz: + case BuiltinFnIdClz: + { + int fn_call_param_count = node->data.fn_call_expr.params.length; + assert(fn_call_param_count == 2); + TypeTableEntry *int_type = get_type_for_type_node(node->data.fn_call_expr.params.at(0)); + assert(int_type->id == TypeTableEntryIdInt); + LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, builtin_fn->id); + LLVMValueRef operand = gen_expr(g, node->data.fn_call_expr.params.at(1)); + LLVMValueRef params[] { + operand, + LLVMConstNull(LLVMInt1Type()), + }; + add_debug_source_node(g, node); + return LLVMBuildCall(g->builder, fn_val, params, 2, ""); + } case BuiltinFnIdAddWithOverflow: case BuiltinFnIdSubWithOverflow: case BuiltinFnIdMulWithOverflow: @@ -1025,11 +1059,12 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) { case PrefixOpDereference: { LLVMValueRef expr = gen_expr(g, expr_node); + assert(expr_type->id == TypeTableEntryIdPointer); if (!type_has_bits(expr_type)) { return nullptr; } else { - add_debug_source_node(g, node); - return LLVMBuildLoad(g->builder, expr, ""); + TypeTableEntry *child_type = expr_type->data.pointer.child_type; + return get_handle_value(g, node, expr, child_type); } } case PrefixOpMaybe: @@ -3590,6 +3625,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn_with_arg_count(g, BuiltinFnIdCUndef, "c_undef", 1); create_builtin_fn_with_arg_count(g, BuiltinFnIdCompileVar, "compile_var", 1); create_builtin_fn_with_arg_count(g, BuiltinFnIdConstEval, "const_eval", 1); + create_builtin_fn_with_arg_count(g, BuiltinFnIdCtz, "ctz", 2); + create_builtin_fn_with_arg_count(g, BuiltinFnIdClz, "clz", 2); } static void init(CodeGen *g, Buf *source_path) { diff --git a/src/link.cpp b/src/link.cpp index 318d6ffa34..5d684c46e6 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -205,6 +205,9 @@ static void construct_linker_job_linux(LinkJob *lj) { if (!g->link_libc && (g->out_type == OutTypeExe || g->out_type == OutTypeLib)) { Buf *builtin_o_path = build_o(g, "builtin"); lj->args.append(buf_ptr(builtin_o_path)); + + Buf *compiler_rt_o_path = build_o(g, "compiler_rt"); + lj->args.append(buf_ptr(compiler_rt_o_path)); } auto it = g->link_table.entry_iterator(); diff --git a/std/compiler_rt.zig b/std/compiler_rt.zig new file mode 100644 index 0000000000..e239580139 --- /dev/null +++ b/std/compiler_rt.zig @@ -0,0 +1,269 @@ +const CHAR_BIT = 8; +const du_int = c_ulonglong; +const di_int = c_longlong; +const si_int = c_int; +const su_int = c_uint; + +const udwords = [2]su_int; +const low = if (@compile_var("is_big_endian")) 1 else 0; +const high = 1 - low; + +export fn __udivdi3(a: du_int, b: du_int) -> du_int { + return __udivmoddi4(a, b, null); +} + +fn du_int_to_udwords(x: du_int) -> udwords { + // TODO ability to take address of params + const x2 = x; + return *(&udwords)(&x2); +} + +export fn __udivmoddi4(a: du_int, b: du_int, maybe_rem: ?&du_int) -> du_int { + const n_uword_bits = @sizeof(su_int) * CHAR_BIT; + const n_udword_bits = @sizeof(du_int) * CHAR_BIT; + var n = du_int_to_udwords(a); + var d = du_int_to_udwords(b); + var q: udwords = undefined; + var r: udwords = undefined; + var sr: c_uint = undefined; + /* special cases, X is unknown, K != 0 */ + if (n[high] == 0) { + if (d[high] == 0) { + /* 0 X + * --- + * 0 X + */ + if (const rem ?= maybe_rem) { + *rem = n[low] % d[low]; + } + return n[low] / d[low]; + } + /* 0 X + * --- + * K X + */ + if (const rem ?= maybe_rem) { + *rem = n[low]; + } + return 0; + } + /* n[high] != 0 */ + if (d[low] == 0) { + if (d[high] == 0) { + /* K X + * --- + * 0 0 + */ + if (var rem ?= maybe_rem) { + *rem = n[high] % d[low]; + } + return n[high] / d[low]; + } + /* d[high] != 0 */ + if (n[low] == 0) { + /* K 0 + * --- + * K 0 + */ + if (var rem ?= maybe_rem) { + r[high] = n[high] % d[high]; + r[low] = 0; + *rem = *(&du_int)(&r[0]); + } + return n[high] / d[high]; + } + /* K K + * --- + * K 0 + */ + // if d is a power of 2 + if ((d[high] & (d[high] - 1)) == 0) { + if (var rem ?= maybe_rem) { + r[low] = n[low]; + r[high] = n[high] & (d[high] - 1); + *rem = *(&du_int)(&r[0]); + } + return n[high] >> @ctz(@typeof(d[high]), d[high]); + } + /* K K + * --- + * K 0 + */ + sr = @clz(su_int, d[high]) - @clz(su_int, n[high]); + /* 0 <= sr <= n_uword_bits - 2 or sr large */ + if (sr > n_uword_bits - 2) { + if (var rem ?= maybe_rem) { + *rem = *(&du_int)(&n[0]); + } + return 0; + } + sr += 1; + /* 1 <= sr <= n_uword_bits - 1 */ + /* q.all = n.all << (n_udword_bits - sr); */ + q[low] = 0; + q[high] = n[low] << (n_uword_bits - sr); + /* r.all = n.all >> sr; */ + r[high] = n[high] >> sr; + r[low] = (n[high] << (n_uword_bits - sr)) | (n[low] >> sr); + } else { + /* d[low] != 0 */ + if (d[high] == 0) { + /* K X + * --- + * 0 K + */ + /* if d is a power of 2 */ + if ((d[low] & (d[low] - 1)) == 0) { + if (var rem ?= maybe_rem) { + *rem = n[low] & (d[low] - 1); + } + if (d[low] == 1) { + return *(&du_int)(&n[0]); + } + sr = @ctz(@typeof(d[low]), d[low]); + q[high] = n[high] >> sr; + q[low] = (n[high] << (n_uword_bits - sr)) | (n[low] >> sr); + return *(&du_int)(&q[0]); + } + /* K X + * --- + * 0 K + */ + sr = 1 + n_uword_bits + @clz(su_int, d[low]) - @clz(su_int, n[high]); + /* 2 <= sr <= n_udword_bits - 1 + * q.all = n.all << (n_udword_bits - sr); + * r.all = n.all >> sr; + */ + if (sr == n_uword_bits) { + q[low] = 0; + q[high] = n[low]; + r[high] = 0; + r[low] = n[high]; + } else if (sr < n_uword_bits) { + // 2 <= sr <= n_uword_bits - 1 + q[low] = 0; + q[high] = n[low] << (n_uword_bits - sr); + r[high] = n[high] >> sr; + r[low] = (n[high] << (n_uword_bits - sr)) | (n[low] >> sr); + } else { + // n_uword_bits + 1 <= sr <= n_udword_bits - 1 + q[low] = n[low] << (n_udword_bits - sr); + q[high] = (n[high] << (n_udword_bits - sr)) | + (n[low] >> (sr - n_uword_bits)); + r[high] = 0; + r[low] = n[high] >> (sr - n_uword_bits); + } + } else { + /* K X + * --- + * K K + */ + sr = @clz(su_int, d[high]) - @clz(su_int, n[high]); + /* 0 <= sr <= n_uword_bits - 1 or sr large */ + if (sr > n_uword_bits - 1) { + if (var rem ?= maybe_rem) { + *rem = *(&du_int)(&n[0]); + } + return 0; + } + sr += 1; + /* 1 <= sr <= n_uword_bits */ + /* q.all = n.all << (n_udword_bits - sr); */ + q[low] = 0; + if (sr == n_uword_bits) { + q[high] = n[low]; + r[high] = 0; + r[low] = n[high]; + } else { + q[high] = n[low] << (n_uword_bits - sr); + r[high] = n[high] >> sr; + r[low] = (n[high] << (n_uword_bits - sr)) | (n[low] >> sr); + } + } + } + /* Not a special case + * q and r are initialized with: + * q.all = n.all << (n_udword_bits - sr); + * r.all = n.all >> sr; + * 1 <= sr <= n_udword_bits - 1 + */ + var carry: su_int = 0; + while (sr > 0) { + /* r:q = ((r:q) << 1) | carry */ + r[high] = (r[high] << 1) | (r[low] >> (n_uword_bits - 1)); + r[low] = (r[low] << 1) | (q[high] >> (n_uword_bits - 1)); + q[high] = (q[high] << 1) | (q[low] >> (n_uword_bits - 1)); + q[low] = (q[low] << 1) | carry; + /* carry = 0; + * if (r.all >= d.all) + * { + * r.all -= d.all; + * carry = 1; + * } + */ + const s: di_int = (di_int)(*(&du_int)(&d[0]) - *(&du_int)(&r[0]) - 1) >> (n_udword_bits - 1); + carry = su_int(s & 1); + *(&du_int)(&r[0]) -= *(&du_int)(&d[0]) & c_ulonglong(s); + + sr -= 1; + } + *(&du_int)(&q[0]) = (*(&du_int)(&q[0]) << 1) | carry; + if (var rem ?= maybe_rem) { + *rem = *(&du_int)(&r[0]); + } + return *(&du_int)(&q[0]); +} + +export fn __umoddi3(a: du_int, b: du_int) -> du_int { + var r: du_int = undefined; + __udivmoddi4(a, b, &r); + return r; +} + + +#attribute("test") +fn test_umoddi3() { + test_one_umoddi3(0, 1, 0); + test_one_umoddi3(2, 1, 0); + test_one_umoddi3(0x8000000000000000, 1, 0x0); + test_one_umoddi3(0x8000000000000000, 2, 0x0); + test_one_umoddi3(0xFFFFFFFFFFFFFFFF, 2, 0x1); +} + +fn test_one_umoddi3(a: du_int, b: du_int, expected_r: du_int) { + const r = __umoddi3(a, b); + assert(r == expected_r); +} + +#attribute("test") +fn test_udivmoddi4() { + const cases = [][4]du_int { + []du_int{0x0000000000000000, 0x0000000000000001, 0x0000000000000000, 0x0000000000000000}, + []du_int{0x0000000080000000, 0x0000000100000001, 0x0000000000000000, 0x0000000080000000}, + []du_int{0x7FFFFFFF00000001, 0x0000000000000001, 0x7FFFFFFF00000001, 0x0000000000000000}, + []du_int{0x7FFFFFFF7FFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000, 0x7FFFFFFF7FFFFFFF}, + []du_int{0x8000000000000002, 0xFFFFFFFFFFFFFFFE, 0x0000000000000000, 0x8000000000000002}, + []du_int{0x80000000FFFFFFFD, 0xFFFFFFFFFFFFFFFD, 0x0000000000000000, 0x80000000FFFFFFFD}, + []du_int{0xFFFFFFFD00000010, 0xFFFFFFFF80000000, 0x0000000000000000, 0xFFFFFFFD00000010}, + []du_int{0xFFFFFFFDFFFFFFFF, 0xFFFFFFFF7FFFFFFF, 0x0000000000000000, 0xFFFFFFFDFFFFFFFF}, + []du_int{0xFFFFFFFE0747AE14, 0xFFFFFFFF0747AE14, 0x0000000000000000, 0xFFFFFFFE0747AE14}, + []du_int{0xFFFFFFFF00000001, 0xFFFFFFFF078644FA, 0x0000000000000000, 0xFFFFFFFF00000001}, + []du_int{0xFFFFFFFF80000000, 0xFFFFFFFF00000010, 0x0000000000000001, 0x000000007FFFFFF0}, + []du_int{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x0000000000000001, 0x0000000000000000}, + }; + + for (cases) |case| { + test_one_udivmoddi4(case[0], case[1], case[2], case[3]); + } +} + +fn test_one_udivmoddi4(a: du_int, b: du_int, expected_q: du_int, expected_r: du_int) { + var r: du_int = undefined; + const q = __udivmoddi4(a, b, &r); + assert(q == expected_q); + assert(r == expected_r); +} + +fn assert(b: bool) { + if (!b) unreachable{}; +} diff --git a/test/self_hosted.zig b/test/self_hosted.zig index a35b2a5ff5..38338cfaf0 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -479,6 +479,20 @@ struct ArrayDotLenConstExpr { const some_array = []u8 {0, 1, 2, 3}; +#attribute("test") +fn count_leading_zeroes() { + assert(@clz(u8, 0b00001010) == 4); + assert(@clz(u8, 0b10001010) == 0); + assert(@clz(u8, 0b00000000) == 8); +} + +#attribute("test") +fn count_trailing_zeroes() { + assert(@ctz(u8, 0b10100000) == 5); + assert(@ctz(u8, 0b10001010) == 1); + assert(@ctz(u8, 0b00000000) == 8); +} + fn assert(b: bool) {