diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b8dc3de69..1bcb32f850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,11 +51,6 @@ jobs: command: clippy args: -- -D warnings - - uses: actions-rs/cargo@v1 - name: cargo test - with: - command: test - - uses: actions-rs/cargo@v1 name: cargo test -- --ignored continue-on-error: true diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index ae6f68e2b1..4cd54e8659 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -21,6 +21,16 @@ fn powInt(base: i64, exp: i64) callconv(.C) i64 { return math.pow(i64, base, exp); } +comptime { @export(acos, .{ .name = math_namespace ++ ".acos", .linkage = .Strong }); } +fn acos(num: f64) callconv(.C) f64 { + return math.acos(num); +} + +comptime { @export(asin, .{ .name = math_namespace ++ ".asin", .linkage = .Strong }); } +fn asin(num: f64) callconv(.C) f64 { + return math.asin(num); +} + // Str.split diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 9ec28ad822..51d6dce519 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -17,6 +17,8 @@ pub fn get_bytes() -> Vec { buffer } +pub const MATH_ASIN: &str = "roc_builtins.math.asin"; +pub const MATH_ACOS: &str = "roc_builtins.math.acos"; pub const MATH_ATAN: &str = "roc_builtins.math.atan"; pub const MATH_IS_FINITE: &str = "roc_builtins.math.is_finite"; pub const MATH_POW_INT: &str = "roc_builtins.math.pow_int"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index f81cb101e0..d32578b1db 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -350,6 +350,18 @@ pub fn types() -> MutMap { top_level_function(vec![float_type()], Box::new(float_type())), ); + // acos : Float -> Float + add_type( + Symbol::NUM_ACOS, + top_level_function(vec![float_type()], Box::new(float_type())), + ); + + // asin : Float -> Float + add_type( + Symbol::NUM_ASIN, + top_level_function(vec![float_type()], Box::new(float_type())), + ); + // Bool module // and : Bool, Bool -> Bool diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index a0f7c6561a..3d7049bb66 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -375,6 +375,18 @@ pub fn types() -> MutMap { unique_function(vec![float_type(star1)], float_type(star2)) }); + // acos : Float -> Float + add_type(Symbol::NUM_ACOS, { + let_tvars! { star1, star2 }; + unique_function(vec![float_type(star1)], float_type(star2)) + }); + + // asin : Float -> Float + add_type(Symbol::NUM_ASIN, { + let_tvars! { star1, star2 }; + unique_function(vec![float_type(star1)], float_type(star2)) + }); + // Bool module // isEq or (==) : Attr * a, Attr * a -> Attr * Bool diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index f189f832bc..910ff0b1f7 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -98,6 +98,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::NUM_POW_INT => num_pow_int, Symbol::NUM_FLOOR => num_floor, Symbol::NUM_ATAN => num_atan, + Symbol::NUM_ACOS => num_acos, + Symbol::NUM_ASIN => num_asin, } } @@ -781,6 +783,46 @@ fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.acos : Float -> Float +fn num_acos(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_float_var = var_store.fresh(); + let ret_float_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumAcos, + args: vec![(arg_float_var, Var(Symbol::ARG_1))], + ret_var: ret_float_var, + }; + + defn( + symbol, + vec![(arg_float_var, Symbol::ARG_1)], + var_store, + body, + ret_float_var, + ) +} + +/// Num.asin : Float -> Float +fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_float_var = var_store.fresh(); + let ret_float_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumAsin, + args: vec![(arg_float_var, Var(Symbol::ARG_1))], + ret_var: ret_float_var, + }; + + defn( + symbol, + vec![(arg_float_var, Symbol::ARG_1)], + var_store, + body, + ret_float_var, + ) +} + /// List.isEmpty : List * -> Bool fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 2c263b5fe6..7f1302f384 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1443,6 +1443,8 @@ fn to_pending_def<'a>( SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { to_pending_def(env, var_store, sub_def, scope, pattern_type) } + + NotYetImplemented(s) => todo!("{}", s), } } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index edbf013919..dcc9e8559d 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -47,6 +47,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { Nested(alias @ Alias { .. }) => Nested(alias), ann @ Annotation(_, _) => Nested(ann), Nested(ann @ Annotation(_, _)) => Nested(ann), + Nested(NotYetImplemented(s)) => todo!("{}", s), + NotYetImplemented(s) => todo!("{}", s), } } diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index 8cd03f076e..aae8679b35 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -20,6 +20,7 @@ impl<'a> Formattable<'a> for Def<'a> { spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline() } Nested(def) => def.is_multiline(), + NotYetImplemented(s) => todo!("{}", s), } } @@ -66,6 +67,7 @@ impl<'a> Formattable<'a> for Def<'a> { fmt_spaces(buf, spaces.iter(), indent); } Nested(def) => def.format(buf, indent), + NotYetImplemented(s) => todo!("{}", s), } } } diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 6b7055cdf1..42b767024a 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1842,6 +1842,252 @@ pub fn create_entry_block_alloca<'a, 'ctx>( builder.build_alloca(basic_type, name) } +fn expose_function_to_host<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_function: FunctionValue<'ctx>, +) { + use inkwell::types::BasicType; + + let roc_wrapper_function = make_exception_catching_wrapper(env, roc_function); + + let roc_function_type = roc_wrapper_function.get_type(); + + // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` + let mut argument_types = roc_function_type.get_param_types(); + let return_type = roc_function_type.get_return_type().unwrap(); + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); + + let c_function_type = env.context.void_type().fn_type(&argument_types, false); + let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap()); + + let c_function = env.module.add_function( + c_function_name.as_str(), + c_function_type, + Some(Linkage::External), + ); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + + builder.position_at_end(entry); + + // drop the final argument, which is the pointer we write the result into + let args = c_function.get_params(); + let output_arg_index = args.len() - 1; + let args = &args[..args.len() - 1]; + + debug_assert_eq!(args.len(), roc_function.get_params().len()); + debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len()); + + let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + let call_result = call_wrapped.try_as_basic_value().left().unwrap(); + + let output_arg = c_function + .get_nth_param(output_arg_index as u32) + .unwrap() + .into_pointer_value(); + + builder.build_store(output_arg, call_result); + + builder.build_return(None); + + // STEP 3: build a {} -> u64 function that gives the size of the return type + let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap()); + + let size_function = env.module.add_function( + size_function_name.as_str(), + size_function_type, + Some(Linkage::External), + ); + + let entry = context.append_basic_block(size_function, "entry"); + + builder.position_at_end(entry); + + let size: BasicValueEnum = return_type.size_of().unwrap().into(); + builder.build_return(Some(&size)); +} + +fn make_exception_catching_wrapper<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_function: FunctionValue<'ctx>, +) -> FunctionValue<'ctx> { + // build the C calling convention wrapper + + let context = env.context; + let builder = env.builder; + + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let roc_function_type = roc_function.get_type(); + let argument_types = roc_function_type.get_param_types(); + + let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap()); + + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function_type.get_return_type().unwrap(), + ], + false, + ); + + let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false); + + // Add main to the module. + let wrapper_function = + env.module + .add_function(&wrapper_function_name, wrapper_function_type, None); + + // our exposed main function adheres to the C calling convention + wrapper_function.set_call_conventions(FAST_CALL_CONV); + + // Add main's body + let basic_block = context.append_basic_block(wrapper_function, "entry"); + let then_block = context.append_basic_block(wrapper_function, "then_block"); + let catch_block = context.append_basic_block(wrapper_function, "catch_block"); + let cont_block = context.append_basic_block(wrapper_function, "cont_block"); + + builder.position_at_end(basic_block); + + let result_alloca = builder.build_alloca(wrapper_return_type, "result"); + + // invoke instead of call, so that we can catch any exeptions thrown in Roc code + let arguments = wrapper_function.get_params(); + let call_result = { + let call = builder.build_invoke( + roc_function, + &arguments, + then_block, + catch_block, + "call_roc_function", + ); + call.set_call_convention(FAST_CALL_CONV); + call.try_as_basic_value().left().unwrap() + }; + + // exception handling + { + builder.position_at_end(catch_block); + + let landing_pad_type = { + let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); + let selector_value = context.i32_type().into(); + + context.struct_type(&[exception_ptr, selector_value], false) + }; + + let info = builder + .build_catch_all_landing_pad( + &landing_pad_type, + &BasicValueEnum::IntValue(context.i8_type().const_zero()), + context.i8_type().ptr_type(AddressSpace::Generic), + "main_landing_pad", + ) + .into_struct_value(); + + let exception_ptr = builder + .build_extract_value(info, 0, "exception_ptr") + .unwrap(); + + let thrown = cxa_begin_catch(env, exception_ptr); + + let error_msg = { + let exception_type = u8_ptr; + let ptr = builder.build_bitcast( + thrown, + exception_type.ptr_type(AddressSpace::Generic), + "cast", + ); + + builder.build_load(ptr.into_pointer_value(), "error_msg") + }; + + let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); + + let return_value = { + let v1 = return_type.const_zero(); + + // flag is non-zero, indicating failure + let flag = context.i64_type().const_int(1, false); + + let v2 = builder + .build_insert_value(v1, flag, 0, "set_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, error_msg, 1, "set_exception") + .unwrap(); + + v3 + }; + + // bitcast result alloca so we can store our concrete type { flag, error_msg } in there + let result_alloca_bitcast = builder + .build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "result_alloca_bitcast", + ) + .into_pointer_value(); + + // store our return value + builder.build_store(result_alloca_bitcast, return_value); + + cxa_end_catch(env); + + builder.build_unconditional_branch(cont_block); + } + + { + builder.position_at_end(then_block); + + let return_value = { + let v1 = wrapper_return_type.const_zero(); + + let v2 = builder + .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") + .unwrap(); + let v3 = builder + .build_insert_value(v2, call_result, 1, "set_call_result") + .unwrap(); + + v3 + }; + + let ptr = builder.build_bitcast( + result_alloca, + wrapper_return_type.ptr_type(AddressSpace::Generic), + "name", + ); + builder.build_store(ptr.into_pointer_value(), return_value); + + builder.build_unconditional_branch(cont_block); + } + + { + builder.position_at_end(cont_block); + + let result = builder.build_load(result_alloca, "result"); + + builder.build_return(Some(&result)); + } + + // MUST set the personality at the very end; + // doing it earlier can cause the personality to be ignored + let personality_func = get_gxx_personality_v0(env); + wrapper_function.set_personality_function(personality_func); + + wrapper_function +} + pub fn build_proc_header<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -1871,19 +2117,79 @@ pub fn build_proc_header<'a, 'ctx, 'env>( .module .add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); + fn_val.set_call_conventions(FAST_CALL_CONV); + if env.exposed_to_host.contains(&symbol) { - // If this is an external-facing function, it'll use the C calling convention - // and external linkage. - fn_val.set_linkage(Linkage::External); - fn_val.set_call_conventions(C_CALL_CONV); - } else { - // If it's an internal-only function, it should use the fast calling conention. - fn_val.set_call_conventions(FAST_CALL_CONV); + expose_function_to_host(env, fn_val); } fn_val } +#[allow(dead_code)] +pub fn build_closure_caller<'a, 'ctx, 'env>( + env: &'a Env<'a, 'ctx, 'env>, + closure_function: FunctionValue<'ctx>, +) { + let context = env.context; + let builder = env.builder; + // asuming the closure has type `a, b, closure_data -> c` + // change that into `a, b, *const closure_data, *mut output -> ()` + + // a function `a, b, closure_data -> RocCallResult` + let wrapped_function = make_exception_catching_wrapper(env, closure_function); + + let closure_function_type = closure_function.get_type(); + let wrapped_function_type = wrapped_function.get_type(); + + let mut arguments = closure_function_type.get_param_types(); + + // require that the closure data is passed by reference + let closure_data_type = arguments.pop().unwrap(); + let closure_data_ptr_type = get_ptr_type(&closure_data_type, AddressSpace::Generic); + arguments.push(closure_data_ptr_type.into()); + + // require that a pointer is passed in to write the result into + let output_type = get_ptr_type( + &wrapped_function_type.get_return_type().unwrap(), + AddressSpace::Generic, + ); + arguments.push(output_type.into()); + + let caller_function_type = env.context.void_type().fn_type(&arguments, false); + let caller_function_name: String = + format!("{}_caller", closure_function.get_name().to_str().unwrap()); + + let caller_function = env.module.add_function( + caller_function_name.as_str(), + caller_function_type, + Some(Linkage::External), + ); + + caller_function.set_call_conventions(C_CALL_CONV); + + let entry = context.append_basic_block(caller_function, "entry"); + + builder.position_at_end(entry); + + let mut parameters = caller_function.get_params(); + let output = parameters.pop().unwrap(); + let closure_data_ptr = parameters.pop().unwrap(); + + let closure_data = + builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data"); + parameters.push(closure_data); + + let call = builder.build_call(wrapped_function, ¶meters, "call_wrapped_function"); + call.set_call_convention(FAST_CALL_CONV); + + let result = call.try_as_basic_value().left().unwrap(); + + builder.build_store(output.into_pointer_value(), result); + + builder.build_return(None); +} + pub fn build_proc<'a, 'ctx, 'env>( env: &'a Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -1907,6 +2213,10 @@ pub fn build_proc<'a, 'ctx, 'env>( // the closure argument (if any) comes in as an opaque sequence of bytes. // we need to cast that to the specific closure data layout that the body expects let value = if let Symbol::ARG_CLOSURE = *arg_symbol { + // generate a caller function (to be used by the host) + // build_closure_caller(env, fn_val); + // builder.position_at_end(entry); + // blindly trust that there is a layout available for the closure data let layout = proc.closure_data_layout.clone().unwrap(); @@ -2172,7 +2482,7 @@ fn run_low_level<'a, 'ctx, 'env>( list_join(env, inplace, parent, list, outer_list_layout) } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor - | NumToFloat | NumIsFinite | NumAtan => { + | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => { debug_assert_eq!(args.len(), 1); let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -2737,6 +3047,8 @@ fn build_float_unary_op<'a, 'ctx, 'env>( ), NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], &bitcode::MATH_IS_FINITE), NumAtan => call_bitcode_fn(NumAtan, env, &[arg.into()], &bitcode::MATH_ATAN), + NumAcos => call_bitcode_fn(NumAcos, env, &[arg.into()], &bitcode::MATH_ACOS), + NumAsin => call_bitcode_fn(NumAsin, env, &[arg.into()], &bitcode::MATH_ASIN), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } @@ -2918,7 +3230,7 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu } fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { - let name = "__cxa_rethrow"; + let name = "__gxx_personality_v0"; let module = env.module; let context = env.context; diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index cb4e966b93..5b24e811ec 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -101,6 +101,8 @@ fn generate_module_doc<'a>( } => (acc, None), Body(_, _) | Nested(_) => (acc, None), + + NotYetImplemented(s) => todo!("{}", s), } } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 10f85bec53..78963ac6af 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -44,6 +44,8 @@ pub enum LowLevel { NumFloor, NumIsFinite, NumAtan, + NumAcos, + NumAsin, Eq, NotEq, And, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 9dc13a4ddc..da89a2fd9f 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -652,6 +652,8 @@ define_builtins! { 42 NUM_ADD_WRAP: "addWrap" 43 NUM_ADD_CHECKED: "addChecked" 44 NUM_ATAN: "atan" + 45 NUM_ACOS: "acos" + 46 NUM_ASIN: "asin" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 9d917abe89..e84ad18566 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -527,6 +527,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { | NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor - | NumToFloat | Not | NumIsFinite | NumAtan => arena.alloc_slice_copy(&[irrelevant]), + | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => { + arena.alloc_slice_copy(&[irrelevant]) + } } } diff --git a/compiler/parse/fuzz/.gitignore b/compiler/parse/fuzz/.gitignore new file mode 100644 index 0000000000..572e03bdf3 --- /dev/null +++ b/compiler/parse/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/compiler/parse/fuzz/Cargo.lock b/compiler/parse/fuzz/Cargo.lock new file mode 100644 index 0000000000..78597e9fca --- /dev/null +++ b/compiler/parse/fuzz/Cargo.lock @@ -0,0 +1,182 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arbitrary" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "im" +version = "14.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696059c87b83c5a258817ecd67c3af915e3ed141891fc35a1e79908801cf0ce7" +dependencies = [ + "bitmaps", + "rand_core 0.5.1", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "im-rc" +version = "14.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303f7e6256d546e01979071417432425f15c1891fb309a5f2d724ee908fabd6e" +dependencies = [ + "bitmaps", + "rand_core 0.5.1", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "inlinable_string" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libfuzzer-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_xoshiro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "roc_collections" +version = "0.1.0" +dependencies = [ + "bumpalo", + "im", + "im-rc", + "wyhash", +] + +[[package]] +name = "roc_module" +version = "0.1.0" +dependencies = [ + "bumpalo", + "inlinable_string", + "lazy_static", + "roc_collections", + "roc_region", +] + +[[package]] +name = "roc_parse" +version = "0.1.0" +dependencies = [ + "bumpalo", + "encode_unicode", + "inlinable_string", + "roc_collections", + "roc_module", + "roc_region", +] + +[[package]] +name = "roc_parse-fuzz" +version = "0.0.0" +dependencies = [ + "bumpalo", + "libfuzzer-sys", + "roc_parse", +] + +[[package]] +name = "roc_region" +version = "0.1.0" + +[[package]] +name = "sized-chunks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wyhash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782a50f48ac4336916227cd199c61c7b42f38d0ad705421b49eb12c74c53ae00" +dependencies = [ + "rand_core 0.4.2", +] diff --git a/compiler/parse/fuzz/Cargo.toml b/compiler/parse/fuzz/Cargo.toml new file mode 100644 index 0000000000..2b60a1683e --- /dev/null +++ b/compiler/parse/fuzz/Cargo.toml @@ -0,0 +1,39 @@ + +[package] +name = "roc_parse-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.3" +bumpalo = { version = "3.2", features = ["collections"] } + +[dependencies.roc_parse] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "fuzz_expr" +path = "fuzz_targets/fuzz_expr.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_defs" +path = "fuzz_targets/fuzz_defs.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_header" +path = "fuzz_targets/fuzz_header.rs" +test = false +doc = false diff --git a/compiler/parse/fuzz/README.md b/compiler/parse/fuzz/README.md new file mode 100644 index 0000000000..c31c048a6d --- /dev/null +++ b/compiler/parse/fuzz/README.md @@ -0,0 +1,11 @@ +To setup fuzzing you will need to install cargo-fuzz and run with rust nightly: + +``` +$ cargo install cargo-fuzz +$ cargo +nightly fuzz run -j -- -dict=dict.txt +``` + +The different targets can be found by running `cargo fuzz list`. + +When a bug is found, it will be reported with commands to run it again and look for a minimized version. +If you are going to file a bug, please minimize the input before filing the bug. diff --git a/compiler/parse/fuzz/dict.txt b/compiler/parse/fuzz/dict.txt new file mode 100644 index 0000000000..c22976ccb2 --- /dev/null +++ b/compiler/parse/fuzz/dict.txt @@ -0,0 +1,36 @@ +"if" +"then" +"else" +"when" +"as" +"is" +"expect" + +"app" +"platform" +"provides" +"requires" +"exposes" +"imports" +"effects" +"interface" + +"|>" +"==" +"!=" +"&&" +"||" +"+" +"*" +"-" +"//" +"/" +"<=" +"<" +">=" +">" +"^" +"%%" +"%" + +"->" \ No newline at end of file diff --git a/compiler/parse/fuzz/fuzz_targets/fuzz_defs.rs b/compiler/parse/fuzz/fuzz_targets/fuzz_defs.rs new file mode 100644 index 0000000000..f02bfc587c --- /dev/null +++ b/compiler/parse/fuzz/fuzz_targets/fuzz_defs.rs @@ -0,0 +1,11 @@ +#![no_main] +use bumpalo::Bump; +use libfuzzer_sys::fuzz_target; +use roc_parse::test_helpers::parse_defs_with; + +fuzz_target!(|data: &[u8]| { + if let Ok(input) = std::str::from_utf8(data) { + let arena = Bump::new(); + let _actual = parse_defs_with(&arena, input.trim()); + } +}); diff --git a/compiler/parse/fuzz/fuzz_targets/fuzz_expr.rs b/compiler/parse/fuzz/fuzz_targets/fuzz_expr.rs new file mode 100644 index 0000000000..d130a0c621 --- /dev/null +++ b/compiler/parse/fuzz/fuzz_targets/fuzz_expr.rs @@ -0,0 +1,11 @@ +#![no_main] +use bumpalo::Bump; +use libfuzzer_sys::fuzz_target; +use roc_parse::test_helpers::parse_expr_with; + +fuzz_target!(|data: &[u8]| { + if let Ok(input) = std::str::from_utf8(data) { + let arena = Bump::new(); + let _actual = parse_expr_with(&arena, input.trim()); + } +}); diff --git a/compiler/parse/fuzz/fuzz_targets/fuzz_header.rs b/compiler/parse/fuzz/fuzz_targets/fuzz_header.rs new file mode 100644 index 0000000000..e04f338c87 --- /dev/null +++ b/compiler/parse/fuzz/fuzz_targets/fuzz_header.rs @@ -0,0 +1,11 @@ +#![no_main] +use bumpalo::Bump; +use libfuzzer_sys::fuzz_target; +use roc_parse::test_helpers::parse_header_with; + +fuzz_target!(|data: &[u8]| { + if let Ok(input) = std::str::from_utf8(data) { + let arena = Bump::new(); + let _actual = parse_header_with(&arena, input.trim()); + } +}); diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 3339057ec9..a227e4a6d9 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -277,6 +277,8 @@ pub enum Def<'a> { /// This is used only to avoid cloning when reordering expressions (e.g. in desugar()). /// It lets us take a (&Def) and create a plain (Def) from it. Nested(&'a Def<'a>), + + NotYetImplemented(&'static str), } #[derive(Debug, Clone, PartialEq)] diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 2d67ee3b33..4120f597f3 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -561,7 +561,7 @@ fn annotation_or_alias<'a>( ann: loc_ann, }, Apply(_, _) => { - panic!("TODO gracefully handle invalid Apply in type annotation"); + Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation") } SpaceAfter(value, spaces_before) => Def::SpaceAfter( arena.alloc(annotation_or_alias(arena, value, region, loc_ann)), @@ -574,19 +574,19 @@ fn annotation_or_alias<'a>( Nested(value) => annotation_or_alias(arena, value, region, loc_ann), PrivateTag(_) => { - panic!("TODO gracefully handle trying to use a private tag as an annotation."); + Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.") } QualifiedIdentifier { .. } => { - panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`"); + Def::NotYetImplemented("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`") } NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => { - panic!("TODO gracefully handle trying to annotate a litera"); + Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera") } Underscore => { - panic!("TODO gracefully handle trying to give a type annotation to an undrscore"); + Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore") } Malformed(_) => { - panic!("TODO translate a malformed pattern into a malformed annotation"); + Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation") } Identifier(ident) => { // This is a regular Annotation @@ -633,7 +633,13 @@ fn parse_def_expr<'a>( )) // `<` because '=' should be same indent (or greater) as the entire def-expr } else if equals_sign_indent < def_start_col { - todo!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col); + Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented(format!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col)), + }, + state, + )) } else { // Indented more beyond the original indent of the entire def-expr. let indented_more = def_start_col + 1; @@ -720,7 +726,15 @@ fn parse_def_signature<'a>( )) // `<` because ':' should be same indent or greater } else if colon_indent < original_indent { - panic!("TODO the : in this declaration seems outdented"); + Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented( + "TODO the : in this declaration seems outdented".to_string(), + ), + }, + state, + )) } else { // Indented more beyond the original indent. let indented_more = original_indent + 1; @@ -1069,11 +1083,12 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located } else { format!("{}.{}", module_name, parts.join(".")) }; - Ok(( Located { region: loc_ident.region, - value: Pattern::Malformed(arena.alloc(malformed_str)), + value: Pattern::Malformed( + String::from_str_in(&malformed_str, &arena).into_bump_str(), + ), }, state, )) @@ -1120,7 +1135,15 @@ mod when { ), move |arena, state, (case_indent, loc_condition)| { if case_indent < min_indent { - panic!("TODO case wasn't indented enough"); + return Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented( + "TODO case wasn't indented enough".to_string(), + ), + }, + state, + )); } // Everything in the branches must be indented at least as much as the case itself. @@ -1178,9 +1201,15 @@ mod when { if alternatives_indented_correctly(&loc_patterns, original_indent) { Ok(((loc_patterns, loc_guard), state)) } else { - panic!( - "TODO additional branch didn't have same indentation as first branch" - ); + Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented( + "TODO additional branch didn't have same indentation as first branch".to_string(), + ), + }, + state, + )) } }, ), @@ -1490,10 +1519,16 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { }); } Err(malformed) => { - panic!( - "TODO early return malformed pattern {:?}", - malformed - ); + return Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented(format!( + "TODO early return malformed pattern {:?}", + malformed + )), + }, + state, + )); } } } diff --git a/compiler/parse/src/lib.rs b/compiler/parse/src/lib.rs index af7f71fbcf..858070e101 100644 --- a/compiler/parse/src/lib.rs +++ b/compiler/parse/src/lib.rs @@ -24,4 +24,5 @@ pub mod number_literal; pub mod pattern; pub mod problems; pub mod string_literal; +pub mod test_helpers; pub mod type_annotation; diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index f83b9cbc46..4270c8d1ec 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -224,6 +224,7 @@ pub enum FailReason { BadUtf8, ReservedKeyword(Region), ArgumentsBeforeEquals(Region), + NotYetImplemented(String), } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index 9c909bb645..85fcd772ff 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -1,8 +1,8 @@ use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment}; use crate::expr; use crate::parser::{ - allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, - ParseResult, Parser, State, + allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail, + FailReason, ParseResult, Parser, State, }; use bumpalo::collections::vec::Vec; use bumpalo::Bump; @@ -279,7 +279,16 @@ where // lines.push(line); // Ok((StrLiteral::Block(lines.into_bump_slice()), state)) - todo!("TODO parse this line in a block string: {:?}", line); + Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented(format!( + "TODO parse this line in a block string: {:?}", + line + )), + }, + state, + )) } Err(reason) => state.fail(reason), }; diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs new file mode 100644 index 0000000000..b7a78e2354 --- /dev/null +++ b/compiler/parse/src/test_helpers.rs @@ -0,0 +1,45 @@ +use crate::ast::{self, Attempting}; +use crate::blankspace::space0_before; +use crate::expr::expr; +use crate::module::{header, module_defs}; +use crate::parser::{loc, Fail, Parser, State}; +use bumpalo::collections::Vec; +use bumpalo::Bump; +use roc_region::all::Located; + +#[allow(dead_code)] +pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +} + +#[allow(dead_code)] +pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + let state = State::new(input.trim().as_bytes(), Attempting::Module); + let answer = header().parse(arena, state); + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} + +#[allow(dead_code)] +pub fn parse_defs_with<'a>( + arena: &'a Bump, + input: &'a str, +) -> Result>>, Fail> { + let state = State::new(input.trim().as_bytes(), Attempting::Module); + let answer = module_defs().parse(arena, state); + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} + +#[allow(dead_code)] +pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { + let state = State::new(input.trim().as_bytes(), Attempting::Module); + let parser = space0_before(loc(expr(0)), 0); + let answer = parser.parse(&arena, state); + + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 03433541fc..d4d14c7bfe 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -4,8 +4,8 @@ use crate::expr::{global_tag, private_tag}; use crate::ident::join_module_parts; use crate::keyword; use crate::parser::{ - allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, - ParseResult, Parser, State, + allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail, + FailReason, ParseResult, Parser, State, }; use bumpalo::collections::string::String; use bumpalo::collections::vec::Vec; @@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located Ok((first, state)) } else { // e.g. `Int,Int` without an arrow and return type - panic!("Invalid function signature") + Err(( + Fail { + attempting: state.attempting, + reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()), + }, + state, + )) } } } diff --git a/compiler/parse/tests/helpers/mod.rs b/compiler/parse/tests/helpers/mod.rs deleted file mode 100644 index c026a340f3..0000000000 --- a/compiler/parse/tests/helpers/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -extern crate bumpalo; - -use self::bumpalo::Bump; -use roc_parse::ast::{self, Attempting}; -use roc_parse::blankspace::space0_before; -use roc_parse::parser::{loc, Fail, Parser, State}; -use roc_region::all::Located; - -#[allow(dead_code)] -pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) -} - -#[allow(dead_code)] -pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { - let state = State::new(input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); - let answer = parser.parse(&arena, state); - - answer - .map(|(loc_expr, _)| loc_expr) - .map_err(|(fail, _)| fail) -} diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index b5e9917822..8c94db8d72 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -11,11 +11,8 @@ extern crate quickcheck_macros; extern crate roc_module; extern crate roc_parse; -mod helpers; - #[cfg(test)] mod test_parse { - use crate::helpers::parse_with; use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; use roc_module::operator::BinOp::*; @@ -33,19 +30,20 @@ mod test_parse { use roc_parse::header::ModuleName; use roc_parse::module::{interface_header, module_defs}; use roc_parse::parser::{Fail, FailReason, Parser, State}; + use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Located, Region}; use std::{f64, i64}; fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) { let arena = Bump::new(); - let actual = parse_with(&arena, input.trim()); + let actual = parse_expr_with(&arena, input.trim()); assert_eq!(Ok(expected_expr), actual); } fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) { let arena = Bump::new(); - let actual = parse_with(&arena, input); + let actual = parse_expr_with(&arena, input); let expected_fail = Fail { reason, attempting }; assert_eq!(Err(expected_fail), actual); @@ -53,7 +51,7 @@ mod test_parse { fn assert_segments Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) { let arena = Bump::new(); - let actual = parse_with(&arena, arena.alloc(input)); + let actual = parse_expr_with(&arena, arena.alloc(input)); let expected_slice = to_expected(&arena); let expected_expr = Expr::Str(Line(&expected_slice)); @@ -77,7 +75,7 @@ mod test_parse { ("\\t", EscapedChar::Tab), ("\\\"", EscapedChar::Quote), ] { - let actual = parse_with(&arena, arena.alloc(to_input(string))); + let actual = parse_expr_with(&arena, arena.alloc(to_input(string))); let expected_slice = to_expected(*escaped, &arena); let expected_expr = Expr::Str(Line(&expected_slice)); @@ -423,7 +421,7 @@ mod test_parse { fields: &[], update: None, }; - let actual = parse_with(&arena, "{}"); + let actual = parse_expr_with(&arena, "{}"); assert_eq!(Ok(expected), actual); } @@ -455,7 +453,7 @@ mod test_parse { fields, }; - let actual = parse_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }"); + let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }"); assert_eq!(Ok(expected), actual); } @@ -470,7 +468,7 @@ mod test_parse { Located::new(0, 0, 2, 3, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "1+2"); + let actual = parse_expr_with(&arena, "1+2"); assert_eq!(Ok(expected), actual); } @@ -484,7 +482,7 @@ mod test_parse { Located::new(0, 0, 2, 3, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "1-2"); + let actual = parse_expr_with(&arena, "1-2"); assert_eq!(Ok(expected), actual); } @@ -498,7 +496,7 @@ mod test_parse { Located::new(0, 0, 7, 8, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "1 + 2"); + let actual = parse_expr_with(&arena, "1 + 2"); assert_eq!(Ok(expected), actual); } @@ -512,7 +510,7 @@ mod test_parse { Located::new(0, 0, 7, 8, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "1 - 2"); + let actual = parse_expr_with(&arena, "1 - 2"); assert_eq!(Ok(expected), actual); } @@ -535,7 +533,7 @@ mod test_parse { Located::new(0, 0, 4, 5, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "x + 2"); + let actual = parse_expr_with(&arena, "x + 2"); assert_eq!(Ok(expected), actual); } @@ -557,7 +555,7 @@ mod test_parse { Located::new(0, 0, 4, 5, Num("2")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "x - 2"); + let actual = parse_expr_with(&arena, "x - 2"); assert_eq!(Ok(expected), actual); } @@ -572,7 +570,7 @@ mod test_parse { Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 \n+ 4"); + let actual = parse_expr_with(&arena, "3 \n+ 4"); assert_eq!(Ok(expected), actual); } @@ -587,7 +585,7 @@ mod test_parse { Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 \n- 4"); + let actual = parse_expr_with(&arena, "3 \n- 4"); assert_eq!(Ok(expected), actual); } @@ -602,7 +600,7 @@ mod test_parse { Located::new(1, 1, 2, 3, spaced_int), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 *\n 4"); + let actual = parse_expr_with(&arena, "3 *\n 4"); assert_eq!(Ok(expected), actual); } @@ -617,7 +615,7 @@ mod test_parse { Located::new(1, 1, 2, 3, spaced_int), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 -\n 4"); + let actual = parse_expr_with(&arena, "3 -\n 4"); assert_eq!(Ok(expected), actual); } @@ -632,7 +630,7 @@ mod test_parse { Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 # 2 × 2\n+ 4"); + let actual = parse_expr_with(&arena, "3 # 2 × 2\n+ 4"); assert_eq!(Ok(expected), actual); } @@ -647,7 +645,7 @@ mod test_parse { Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 # test!\n+ 4"); + let actual = parse_expr_with(&arena, "3 # test!\n+ 4"); assert_eq!(Ok(expected), actual); } @@ -662,7 +660,7 @@ mod test_parse { Located::new(1, 1, 1, 3, spaced_int), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "12 * # test!\n 92"); + let actual = parse_expr_with(&arena, "12 * # test!\n 92"); assert_eq!(Ok(expected), actual); } @@ -678,7 +676,7 @@ mod test_parse { Located::new(3, 3, 2, 3, spaced_int2), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "3 \n+ \n\n 4"); + let actual = parse_expr_with(&arena, "3 \n+ \n\n 4"); assert_eq!(Ok(expected), actual); } @@ -702,7 +700,7 @@ mod test_parse { Located::new(0, 0, 3, 4, var2), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "x- y"); + let actual = parse_expr_with(&arena, "x- y"); assert_eq!(Ok(expected), actual); } @@ -716,7 +714,7 @@ mod test_parse { Located::new(0, 0, 4, 5, Num("5")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "-12-5"); + let actual = parse_expr_with(&arena, "-12-5"); assert_eq!(Ok(expected), actual); } @@ -730,7 +728,7 @@ mod test_parse { Located::new(0, 0, 3, 5, Num("11")), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "10*11"); + let actual = parse_expr_with(&arena, "10*11"); assert_eq!(Ok(expected), actual); } @@ -749,7 +747,7 @@ mod test_parse { Located::new(0, 0, 3, 9, BinOp(inner)), )); let expected = BinOp(outer); - let actual = parse_with(&arena, "31*42+534"); + let actual = parse_expr_with(&arena, "31*42+534"); assert_eq!(Ok(expected), actual); } @@ -771,7 +769,7 @@ mod test_parse { Located::new(0, 0, 3, 4, var2), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "x==y"); + let actual = parse_expr_with(&arena, "x==y"); assert_eq!(Ok(expected), actual); } @@ -793,7 +791,7 @@ mod test_parse { Located::new(0, 0, 5, 6, var2), )); let expected = BinOp(tuple); - let actual = parse_with(&arena, "x == y"); + let actual = parse_expr_with(&arena, "x == y"); assert_eq!(Ok(expected), actual); } @@ -807,7 +805,7 @@ mod test_parse { module_name: "", ident: "whee", }; - let actual = parse_with(&arena, "whee"); + let actual = parse_expr_with(&arena, "whee"); assert_eq!(Ok(expected), actual); } @@ -819,7 +817,7 @@ mod test_parse { module_name: "", ident: "whee", })); - let actual = parse_with(&arena, "(whee)"); + let actual = parse_expr_with(&arena, "(whee)"); assert_eq!(Ok(expected), actual); } @@ -831,7 +829,7 @@ mod test_parse { module_name: "One.Two", ident: "whee", }; - let actual = parse_with(&arena, "One.Two.whee"); + let actual = parse_expr_with(&arena, "One.Two.whee"); assert_eq!(Ok(expected), actual); } @@ -842,7 +840,7 @@ mod test_parse { fn basic_global_tag() { let arena = Bump::new(); let expected = Expr::GlobalTag("Whee"); - let actual = parse_with(&arena, "Whee"); + let actual = parse_expr_with(&arena, "Whee"); assert_eq!(Ok(expected), actual); } @@ -851,7 +849,7 @@ mod test_parse { fn basic_private_tag() { let arena = Bump::new(); let expected = Expr::PrivateTag("@Whee"); - let actual = parse_with(&arena, "@Whee"); + let actual = parse_expr_with(&arena, "@Whee"); assert_eq!(Ok(expected), actual); } @@ -867,7 +865,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "@Whee 12 34"); + let actual = parse_expr_with(&arena, "@Whee 12 34"); assert_eq!(Ok(expected), actual); } @@ -883,7 +881,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "Whee 12 34"); + let actual = parse_expr_with(&arena, "Whee 12 34"); assert_eq!(Ok(expected), actual); } @@ -901,7 +899,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "Whee (12) (34)"); + let actual = parse_expr_with(&arena, "Whee (12) (34)"); assert_eq!(Ok(expected), actual); } @@ -910,7 +908,7 @@ mod test_parse { fn qualified_global_tag() { let arena = Bump::new(); let expected = Expr::MalformedIdent("One.Two.Whee"); - let actual = parse_with(&arena, "One.Two.Whee"); + let actual = parse_expr_with(&arena, "One.Two.Whee"); assert_eq!(Ok(expected), actual); } @@ -920,7 +918,7 @@ mod test_parse { // fn qualified_private_tag() { // let arena = Bump::new(); // let expected = Expr::MalformedIdent("One.Two.@Whee"); - // let actual = parse_with(&arena, "One.Two.@Whee"); + // let actual = parse_expr_with(&arena, "One.Two.@Whee"); // assert_eq!(Ok(expected), actual); // } @@ -931,7 +929,7 @@ mod test_parse { let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing")); let patterns = &[pattern]; let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42")))); - let actual = parse_with(&arena, "\\Thing -> 42"); + let actual = parse_expr_with(&arena, "\\Thing -> 42"); assert_eq!(Ok(expected), actual); } @@ -940,7 +938,7 @@ mod test_parse { fn private_qualified_tag() { let arena = Bump::new(); let expected = Expr::MalformedIdent("@One.Two.Whee"); - let actual = parse_with(&arena, "@One.Two.Whee"); + let actual = parse_expr_with(&arena, "@One.Two.Whee"); assert_eq!(Ok(expected), actual); } @@ -952,7 +950,7 @@ mod test_parse { let arena = Bump::new(); let elems = &[]; let expected = List(elems); - let actual = parse_with(&arena, "[]"); + let actual = parse_expr_with(&arena, "[]"); assert_eq!(Ok(expected), actual); } @@ -963,7 +961,7 @@ mod test_parse { let arena = Bump::new(); let elems = &[]; let expected = List(elems); - let actual = parse_with(&arena, "[ ]"); + let actual = parse_expr_with(&arena, "[ ]"); assert_eq!(Ok(expected), actual); } @@ -973,7 +971,7 @@ mod test_parse { let arena = Bump::new(); let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; let expected = List(elems); - let actual = parse_with(&arena, "[1]"); + let actual = parse_expr_with(&arena, "[1]"); assert_eq!(Ok(expected), actual); } @@ -983,7 +981,7 @@ mod test_parse { let arena = Bump::new(); let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; let expected = List(elems); - let actual = parse_with(&arena, "[ 1 ]"); + let actual = parse_expr_with(&arena, "[ 1 ]"); assert_eq!(Ok(expected), actual); } @@ -998,7 +996,7 @@ mod test_parse { ident: "rec", }; let expected = Access(arena.alloc(var), "field"); - let actual = parse_with(&arena, "rec.field"); + let actual = parse_expr_with(&arena, "rec.field"); assert_eq!(Ok(expected), actual); } @@ -1011,7 +1009,7 @@ mod test_parse { ident: "rec", })); let expected = Access(arena.alloc(paren_var), "field"); - let actual = parse_with(&arena, "(rec).field"); + let actual = parse_expr_with(&arena, "(rec).field"); assert_eq!(Ok(expected), actual); } @@ -1024,7 +1022,7 @@ mod test_parse { ident: "rec", })); let expected = Access(arena.alloc(paren_var), "field"); - let actual = parse_with(&arena, "(One.Two.rec).field"); + let actual = parse_expr_with(&arena, "(One.Two.rec).field"); assert_eq!(Ok(expected), actual); } @@ -1040,7 +1038,7 @@ mod test_parse { arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), "ghi", ); - let actual = parse_with(&arena, "rec.abc.def.ghi"); + let actual = parse_expr_with(&arena, "rec.abc.def.ghi"); assert_eq!(Ok(expected), actual); } @@ -1056,7 +1054,7 @@ mod test_parse { arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), "ghi", ); - let actual = parse_with(&arena, "One.Two.rec.abc.def.ghi"); + let actual = parse_expr_with(&arena, "One.Two.rec.abc.def.ghi"); assert_eq!(Ok(expected), actual); } @@ -1077,7 +1075,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "whee 1"); + let actual = parse_expr_with(&arena, "whee 1"); assert_eq!(Ok(expected), actual); } @@ -1102,7 +1100,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "whee 12 34"); + let actual = parse_expr_with(&arena, "whee 12 34"); assert_eq!(Ok(expected), actual); } @@ -1155,7 +1153,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "a b c d"); + let actual = parse_expr_with(&arena, "a b c d"); assert_eq!(Ok(expected), actual); } @@ -1174,7 +1172,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "(whee) 1"); + let actual = parse_expr_with(&arena, "(whee) 1"); assert_eq!(Ok(expected), actual); } @@ -1191,7 +1189,7 @@ mod test_parse { }; let loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr); let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); - let actual = parse_with(&arena, "-foo"); + let actual = parse_expr_with(&arena, "-foo"); assert_eq!(Ok(expected), actual); } @@ -1206,7 +1204,7 @@ mod test_parse { }; let loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr); let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); - let actual = parse_with(&arena, "!blah"); + let actual = parse_expr_with(&arena, "!blah"); assert_eq!(Ok(expected), actual); } @@ -1242,7 +1240,7 @@ mod test_parse { CalledVia::Space, ); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); - let actual = parse_with(&arena, "-whee 12 foo"); + let actual = parse_expr_with(&arena, "-whee 12 foo"); assert_eq!(Ok(expected), actual); } @@ -1278,7 +1276,7 @@ mod test_parse { CalledVia::Space, ); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); - let actual = parse_with(&arena, "!whee 12 foo"); + let actual = parse_expr_with(&arena, "!whee 12 foo"); assert_eq!(Ok(expected), actual); } @@ -1314,7 +1312,7 @@ mod test_parse { CalledVia::Space, ))); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); - let actual = parse_with(&arena, "-(whee 12 foo)"); + let actual = parse_expr_with(&arena, "-(whee 12 foo)"); assert_eq!(Ok(expected), actual); } @@ -1350,7 +1348,7 @@ mod test_parse { CalledVia::Space, ))); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); - let actual = parse_with(&arena, "!(whee 12 foo)"); + let actual = parse_expr_with(&arena, "!(whee 12 foo)"); assert_eq!(Ok(expected), actual); } @@ -1377,7 +1375,7 @@ mod test_parse { args, CalledVia::Space, ); - let actual = parse_with(&arena, "whee 12 -foo"); + let actual = parse_expr_with(&arena, "whee 12 -foo"); assert_eq!(Ok(expected), actual); } @@ -1394,7 +1392,7 @@ mod test_parse { let access = Access(arena.alloc(var), "field"); let loc_access = Located::new(0, 0, 1, 11, access); let expected = UnaryOp(arena.alloc(loc_access), loc_op); - let actual = parse_with(&arena, "-rec1.field"); + let actual = parse_expr_with(&arena, "-rec1.field"); assert_eq!(Ok(expected), actual); } @@ -1407,7 +1405,7 @@ mod test_parse { let pattern = Located::new(0, 0, 1, 2, Identifier("a")); let patterns = &[pattern]; let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); - let actual = parse_with(&arena, "\\a -> 42"); + let actual = parse_expr_with(&arena, "\\a -> 42"); assert_eq!(Ok(expected), actual); } @@ -1418,7 +1416,7 @@ mod test_parse { let pattern = Located::new(0, 0, 1, 2, Underscore); let patterns = &[pattern]; let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); - let actual = parse_with(&arena, "\\_ -> 42"); + let actual = parse_expr_with(&arena, "\\_ -> 42"); assert_eq!(Ok(expected), actual); } @@ -1429,7 +1427,7 @@ mod test_parse { // underscore in an argument name, it would parse as three arguments // (and would ignore the underscore as if it had been blank space). let arena = Bump::new(); - let actual = parse_with(&arena, "\\the_answer -> 42"); + let actual = parse_expr_with(&arena, "\\the_answer -> 42"); assert_eq!(Ok(MalformedClosure), actual); } @@ -1441,7 +1439,7 @@ mod test_parse { let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); let patterns = &[arg1, arg2]; let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42")))); - let actual = parse_with(&arena, "\\a, b -> 42"); + let actual = parse_expr_with(&arena, "\\a, b -> 42"); assert_eq!(Ok(expected), actual); } @@ -1457,7 +1455,7 @@ mod test_parse { arena.alloc(patterns), arena.alloc(Located::new(0, 0, 12, 14, Num("42"))), ); - let actual = parse_with(&arena, "\\a, b, c -> 42"); + let actual = parse_expr_with(&arena, "\\a, b, c -> 42"); assert_eq!(Ok(expected), actual); } @@ -1472,7 +1470,7 @@ mod test_parse { arena.alloc(patterns), arena.alloc(Located::new(0, 0, 9, 11, Num("42"))), ); - let actual = parse_with(&arena, "\\_, _ -> 42"); + let actual = parse_expr_with(&arena, "\\_, _ -> 42"); assert_eq!(Ok(expected), actual); } @@ -2040,7 +2038,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" @@ -2084,7 +2082,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" @@ -2133,7 +2131,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" @@ -2183,7 +2181,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" @@ -2490,7 +2488,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" @@ -2535,7 +2533,7 @@ mod test_parse { }; let loc_cond = Located::new(0, 0, 5, 6, var); let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_with( + let actual = parse_expr_with( &arena, indoc!( r#" diff --git a/examples/closure/Closure.roc b/examples/closure/Closure.roc new file mode 100644 index 0000000000..253ca727ba --- /dev/null +++ b/examples/closure/Closure.roc @@ -0,0 +1,8 @@ +app Closure provides [ closure ] imports [] + +closure : {} -> Int +closure = + x = 42 + + \{} -> x + diff --git a/examples/closure/platform/Cargo.lock b/examples/closure/platform/Cargo.lock new file mode 100644 index 0000000000..c386bb6c4a --- /dev/null +++ b/examples/closure/platform/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "roc_std 0.1.0", +] + +[[package]] +name = "libc" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" diff --git a/examples/closure/platform/Cargo.toml b/examples/closure/platform/Cargo.toml new file mode 100644 index 0000000000..70f3c1f86c --- /dev/null +++ b/examples/closure/platform/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["Richard Feldman "] +edition = "2018" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +roc_std = { path = "../../../roc_std" } + +[workspace] diff --git a/examples/closure/platform/host.c b/examples/closure/platform/host.c new file mode 100644 index 0000000000..0378c69589 --- /dev/null +++ b/examples/closure/platform/host.c @@ -0,0 +1,7 @@ +#include + +extern int rust_main(); + +int main() { + return rust_main(); +} diff --git a/examples/closure/platform/src/lib.rs b/examples/closure/platform/src/lib.rs new file mode 100644 index 0000000000..e0f10ace7c --- /dev/null +++ b/examples/closure/platform/src/lib.rs @@ -0,0 +1,102 @@ +use std::alloc::Layout; +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::os::raw::c_char; +use std::time::SystemTime; +use RocCallResult::*; + +extern "C" { + #[link_name = "closure_1_exposed"] + fn make_closure(output: *mut u8) -> (); + + // #[link_name = "0_1_caller"] + // fn call_closure_0(unit: (), closure_data: *const u8, output: *mut u8) -> (); + + #[link_name = "closure_1_size"] + fn closure_size() -> i64; +} + +#[no_mangle] +pub fn rust_main() -> isize { + println!("Running Roc closure"); + let start_time = SystemTime::now(); + + let size = unsafe { closure_size() } as usize; + let layout = Layout::array::(size).unwrap(); + let roc_closure = unsafe { + let buffer = std::alloc::alloc(layout); + + make_closure(buffer); + + type CLOSURE_DATA = i64; + let output = &*(buffer as *mut RocCallResult<(fn(CLOSURE_DATA) -> i64, CLOSURE_DATA)>); + + match output.into() { + Ok((function_pointer, closure_data)) => { + std::alloc::dealloc(buffer, layout); + + move || function_pointer(closure_data) + } + Err(msg) => { + std::alloc::dealloc(buffer, layout); + + panic!("Roc failed with message: {}", msg); + } + } + }; + let answer = roc_closure(); + let end_time = SystemTime::now(); + let duration = end_time.duration_since(start_time).unwrap(); + + println!( + "Roc closure took {:.4} ms to compute this answer: {:?}", + duration.as_secs_f64() * 1000.0, + // truncate the answer, so stdout is not swamped + answer + ); + + // Exit code + 0 +} + +#[repr(u64)] +pub enum RocCallResult { + Success(T), + Failure(*mut c_char), +} + +impl Into> for RocCallResult { + fn into(self) -> Result { + match self { + Success(value) => Ok(value), + Failure(failure) => Err({ + let raw = unsafe { CString::from_raw(failure) }; + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }), + } + } +} + +impl Into> for &RocCallResult { + fn into(self) -> Result { + match self { + Success(value) => Ok(*value), + Failure(failure) => Err({ + let raw = unsafe { CString::from_raw(*failure) }; + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }), + } + } +} diff --git a/examples/hello-world/platform/src/lib.rs b/examples/hello-world/platform/src/lib.rs index 07375c7b46..2f1dbfb854 100644 --- a/examples/hello-world/platform/src/lib.rs +++ b/examples/hello-world/platform/src/lib.rs @@ -1,17 +1,27 @@ +use roc_std::RocCallResult; use roc_std::RocStr; use std::str; extern "C" { - #[link_name = "main_1"] - fn main() -> RocStr; + #[link_name = "main_1_exposed"] + fn say_hello(output: &mut RocCallResult) -> (); } #[no_mangle] pub fn rust_main() -> isize { - println!( - "Roc says: {}", - str::from_utf8(unsafe { main().as_slice() }).unwrap() - ); + let answer = unsafe { + use std::mem::MaybeUninit; + let mut output: MaybeUninit> = MaybeUninit::uninit(); + + say_hello(&mut *output.as_mut_ptr()); + + match output.assume_init().into() { + Ok(value) => value, + Err(msg) => panic!("roc failed with message {}", msg), + } + }; + + println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap()); // Exit code 0 diff --git a/examples/multi-module/platform/src/lib.rs b/examples/multi-module/platform/src/lib.rs index a7b310f7ef..5a9aba13c7 100644 --- a/examples/multi-module/platform/src/lib.rs +++ b/examples/multi-module/platform/src/lib.rs @@ -1,9 +1,10 @@ +use roc_std::RocCallResult; use roc_std::RocList; use std::time::SystemTime; extern "C" { - #[link_name = "quicksort_1"] - fn quicksort(list: RocList) -> RocList; + #[link_name = "quicksort_1_exposed"] + fn quicksort(list: RocList, output: &mut RocCallResult>) -> (); } const NUM_NUMS: usize = 100; @@ -24,7 +25,17 @@ pub fn rust_main() -> isize { println!("Running Roc quicksort on {} numbers...", nums.len()); let start_time = SystemTime::now(); - let answer = unsafe { quicksort(nums) }; + let answer = unsafe { + use std::mem::MaybeUninit; + let mut output = MaybeUninit::uninit(); + + quicksort(nums, &mut *output.as_mut_ptr()); + + match output.assume_init().into() { + Ok(value) => value, + Err(msg) => panic!("roc failed with message: {}", msg), + } + }; let end_time = SystemTime::now(); let duration = end_time.duration_since(start_time).unwrap(); diff --git a/examples/quicksort/platform/src/lib.rs b/examples/quicksort/platform/src/lib.rs index a7b310f7ef..4ec4565f5b 100644 --- a/examples/quicksort/platform/src/lib.rs +++ b/examples/quicksort/platform/src/lib.rs @@ -1,9 +1,10 @@ +use roc_std::RocCallResult; use roc_std::RocList; use std::time::SystemTime; extern "C" { - #[link_name = "quicksort_1"] - fn quicksort(list: RocList) -> RocList; + #[link_name = "quicksort_1_exposed"] + fn quicksort(list: RocList, output: &mut RocCallResult>) -> (); } const NUM_NUMS: usize = 100; @@ -24,7 +25,18 @@ pub fn rust_main() -> isize { println!("Running Roc quicksort on {} numbers...", nums.len()); let start_time = SystemTime::now(); - let answer = unsafe { quicksort(nums) }; + let answer = unsafe { + use std::mem::MaybeUninit; + let mut output = MaybeUninit::uninit(); + + quicksort(nums, &mut *output.as_mut_ptr()); + + match output.assume_init().into() { + Ok(value) => value, + Err(msg) => panic!("roc failed with message {}", msg), + } + }; + let end_time = SystemTime::now(); let duration = end_time.duration_since(start_time).unwrap(); diff --git a/examples/shared-quicksort/platform/Cargo.lock b/examples/shared-quicksort/platform/Cargo.lock new file mode 100644 index 0000000000..c386bb6c4a --- /dev/null +++ b/examples/shared-quicksort/platform/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "roc_std 0.1.0", +] + +[[package]] +name = "libc" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" diff --git a/examples/shared-quicksort/platform/Cargo.toml b/examples/shared-quicksort/platform/Cargo.toml new file mode 100644 index 0000000000..70f3c1f86c --- /dev/null +++ b/examples/shared-quicksort/platform/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["Richard Feldman "] +edition = "2018" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +roc_std = { path = "../../../roc_std" } + +[workspace] diff --git a/examples/shared-quicksort/platform/README.md b/examples/shared-quicksort/platform/README.md deleted file mode 100644 index f51d79714d..0000000000 --- a/examples/shared-quicksort/platform/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Rebuilding the host from source - -Here are the current steps to rebuild this host. These -steps can likely be moved into a `build.rs` script after -turning `host.rs` into a `cargo` project, but that hasn't -been attempted yet. - -## Compile the Rust and C sources - -Currently this host has both a `host.rs` and a `host.c`. -This is only because we haven't figured out a way to convince -Rust to emit a `.o` file that doesn't define a `main` entrypoint, -but which is capable of being linked into one later. - -As a workaround, we have `host.rs` expose a function called -`rust_main` instead of `main`, and all `host.c` does is provide -an actual `main` which imports and then calls `rust_main` from -the compiled `host.rs`. It's not the most elegant workaround, -but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4) -didn't turn up any nicer approaches. Maybe they're out there though! - -To make this workaround happen, we need to compile both `host.rs` -and `host.c`. First, `cd` into `platform/host/src/` and then run: - -``` -$ clang -c host.c -o c_host.o -$ rustc host.rs -o rust_host.o -``` - -Now we should have `c_host.o` and `rust_host.o` in the curent directory. - -## Link together the `.o` files - -Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so: - -``` -$ ld -r c_host.o rust_host.o -o host.o -``` - -Move `host.o` into the appropriate `platform/` subdirectory -based on your architecture and operating system. For example, -on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory. - -## All done! - -Congratulations! You now have an updated host. - -It's now fine to delete `c_host.o` and `rust_host.o`, -since they were only needed to produce `host.o`. diff --git a/examples/shared-quicksort/platform/build.sh b/examples/shared-quicksort/platform/build.sh deleted file mode 100755 index 010c502222..0000000000 --- a/examples/shared-quicksort/platform/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# compile c_host.o and rust_host.o -clang -c host.c -o c_host.o -rustc host.rs -o rust_host.o - -# link them together into host.o -ld -r c_host.o rust_host.o -o host.o - -# clean up -rm -f c_host.o -rm -f rust_host.o diff --git a/examples/shared-quicksort/platform/host.rs b/examples/shared-quicksort/platform/host.rs deleted file mode 100644 index 53b46f036d..0000000000 --- a/examples/shared-quicksort/platform/host.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![crate_type = "staticlib"] - -use std::time::SystemTime; - -extern "C" { - #[allow(improper_ctypes)] - #[link_name = "quicksort_1"] - fn quicksort(list: Box<[i64]>) -> Box<[i64]>; -} - -const NUM_NUMS: usize = 10_000; - -#[no_mangle] -pub fn rust_main() -> isize { - let nums: Box<[i64]> = { - let mut nums = Vec::with_capacity(NUM_NUMS); - - for index in 0..nums.capacity() { - let num = index as i64 % 123; - - nums.push(num); - } - nums.into() - }; - - println!("Running Roc quicksort on {} numbers...", nums.len()); - let start_time = SystemTime::now(); - let answer = unsafe { quicksort(nums) }; - let end_time = SystemTime::now(); - let duration = end_time.duration_since(start_time).unwrap(); - - println!( - "Roc quicksort took {:.4} ms to compute this answer: {:?}", - duration.as_secs_f64() * 1000.0, - // truncate the answer, so stdout is not swamped - &answer[0..20] - ); - - // the pointer is to the first _element_ of the list, - // but the refcount precedes it. Thus calling free() on - // this pointer would segfault/cause badness. Therefore, we - // leak it for now - Box::leak(answer); - - // Exit code - 0 -} diff --git a/examples/shared-quicksort/platform/src/lib.rs b/examples/shared-quicksort/platform/src/lib.rs new file mode 100644 index 0000000000..4ec4565f5b --- /dev/null +++ b/examples/shared-quicksort/platform/src/lib.rs @@ -0,0 +1,52 @@ +use roc_std::RocCallResult; +use roc_std::RocList; +use std::time::SystemTime; + +extern "C" { + #[link_name = "quicksort_1_exposed"] + fn quicksort(list: RocList, output: &mut RocCallResult>) -> (); +} + +const NUM_NUMS: usize = 100; + +#[no_mangle] +pub fn rust_main() -> isize { + let nums: RocList = { + let mut nums = Vec::with_capacity(NUM_NUMS); + + for index in 0..nums.capacity() { + let num = index as i64 % 12; + + nums.push(num); + } + + RocList::from_slice(&nums) + }; + + println!("Running Roc quicksort on {} numbers...", nums.len()); + let start_time = SystemTime::now(); + let answer = unsafe { + use std::mem::MaybeUninit; + let mut output = MaybeUninit::uninit(); + + quicksort(nums, &mut *output.as_mut_ptr()); + + match output.assume_init().into() { + Ok(value) => value, + Err(msg) => panic!("roc failed with message {}", msg), + } + }; + + let end_time = SystemTime::now(); + let duration = end_time.duration_since(start_time).unwrap(); + + println!( + "Roc quicksort took {:.4} ms to compute this answer: {:?}", + duration.as_secs_f64() * 1000.0, + // truncate the answer, so stdout is not swamped + &answer.as_slice()[0..20] + ); + + // Exit code + 0 +} diff --git a/roc_std/.gitignore b/roc_std/.gitignore new file mode 100644 index 0000000000..e0292b19e0 --- /dev/null +++ b/roc_std/.gitignore @@ -0,0 +1,2 @@ +*.o +*.a diff --git a/roc_std/build.rs b/roc_std/build.rs new file mode 100644 index 0000000000..6625c9c3de --- /dev/null +++ b/roc_std/build.rs @@ -0,0 +1,48 @@ +// Adapted from https://github.com/TheDan64/scoped_alloca +// by Daniel Kolsoi, licensed under the Apache License 2.0 +// Thank you, Dan! + +use std::env; +use std::fs::create_dir; +use std::path::Path; +use std::process::Command; + +fn main() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let cargo_dir = Path::new(manifest_dir.as_str()); + let lib_dir = cargo_dir.join("lib"); + let alloca_c = cargo_dir.join("src/alloca.c"); + let alloca_o = lib_dir.join("alloca.o"); + let liballoca_a = lib_dir.join("liballoca.a"); + + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + + // No need to recompile alloca static lib every time. We could + // add a feature flag to do so if needed, though + if liballoca_a.is_file() { + return; + } + + if !lib_dir.is_dir() { + create_dir(&lib_dir).unwrap(); + } + + let clang_output = Command::new("clang") + .arg("-c") + .arg(alloca_c) + .arg("-o") + .arg(&alloca_o) + .output() + .expect("Could not execute clang"); + + assert!(clang_output.status.success(), "{:?}", clang_output); + + let ar_output = Command::new("ar") + .arg("-q") + .arg(liballoca_a) + .arg(alloca_o) + .output() + .expect("Could not execute ar"); + + assert!(ar_output.status.success(), "{:?}", ar_output); +} diff --git a/roc_std/src/alloca.c b/roc_std/src/alloca.c new file mode 100644 index 0000000000..e8bf7ee670 --- /dev/null +++ b/roc_std/src/alloca.c @@ -0,0 +1,9 @@ +#include + +// From https://github.com/TheDan64/scoped_alloca +// by Daniel Kolsoi, licensed under the Apache License 2.0 +// Thank you, Dan! + +void *c_alloca(size_t bytes) { + return alloca(bytes); +} diff --git a/roc_std/src/alloca.rs b/roc_std/src/alloca.rs new file mode 100644 index 0000000000..48bc736ee0 --- /dev/null +++ b/roc_std/src/alloca.rs @@ -0,0 +1,124 @@ +// Adapted from https://github.com/TheDan64/scoped_alloca +// by Daniel Kolsoi, licensed under the Apache License 2.0 +// Thank you, Dan! + +use libc::{c_void, size_t}; + +#[link(name = "alloca")] +extern "C" { + #[no_mangle] + fn c_alloca(_: size_t) -> *mut c_void; +} + +/// This calls C's `alloca` function to allocate some bytes on the stack, +/// then provides those bytes to the given callback function. +/// +/// It provides a `&mut c_void` to reflect the lifetime of that memory +/// (it only lives for the duration of the callback), which you'll probably +/// want to cast to something else. To cast `ptr` to the type you want, +/// use `&mut *(ptr as *mut _ as *mut _)` - for example: +/// +/// ```rust,ignore +/// with_stack_bytes(size_of::(), |ptr| { +/// let foo: &mut Foo = &mut *(ptr as *mut _ as *mut _); +/// ``` +/// +/// Yes, both `as *mut _` casts are necessary! The first one casts it to +/// a `*mut c_void` and the second one casts it to the pointer type you want. +/// Finally, the `&mut *` converts it from a pointer to a mutable reference. +/// +/// # Safety +/// +/// This function is `#[inline(always)]`, which means it will allocate +/// the bytes on the stack of whoever calls this function. +/// +/// Naturally, if you give this a large number of bytes, it may cause +/// stack overflows, so be careful! +/// +/// Due to how Rust FFI works with inlining, in debug builds this actually +/// allocates on the heap (using `malloc`) and then deallocates the memory +/// (using `free`) before the callback returns. In debug builds, this can lead +/// to memory leaks if the callback panics - but release builds should be fine, +/// because they only ever allocate on the stack. +/// +#[inline(always)] +pub unsafe fn with_stack_bytes(bytes: usize, callback: F) -> R +where + F: FnOnce(&mut c_void) -> R, +{ + let ptr = malloc_or_alloca(bytes); + let answer = callback(&mut *ptr); + + free_or_noop(ptr); + + answer +} + +#[cfg(debug_assertions)] +#[inline(always)] +unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void { + libc::malloc(bytes) +} + +#[cfg(not(debug_assertions))] +#[inline(always)] +unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void { + c_alloca(bytes) +} + +#[cfg(debug_assertions)] +#[inline(always)] +unsafe fn free_or_noop(ptr: *mut c_void) { + libc::free(ptr) +} + +#[cfg(not(debug_assertions))] +#[inline(always)] +fn free_or_noop(_ptr: *mut c_void) { + // In release builds, we'll have used alloca, + // so there's nothing to free. +} + +#[cfg(test)] +mod tests { + use super::with_stack_bytes; + use core::mem::size_of; + + #[repr(C)] + #[derive(Debug, PartialEq)] + struct TestStruct { + x: u8, + y: u16, + z: u64, + } + + #[test] + fn test_alloca() { + let test_struct = TestStruct { + x: 123, + y: 4567, + z: 89012345678, + }; + let sum: u64 = unsafe { + with_stack_bytes(size_of::(), |ptr| { + // Surprisingly, both casts are necessary; the first one + // turns it from &mut c_void to *mut c_void, and the second + // one turns it into *mut TestStruct + let new_ts: &mut TestStruct = &mut *(ptr as *mut _ as *mut _); + + new_ts.x = test_struct.x; + new_ts.y = test_struct.y; + new_ts.z = test_struct.z; + + assert_eq!(new_ts, &test_struct); + + new_ts.x as u64 + new_ts.y as u64 + new_ts.z as u64 + }) + }; + + assert_eq!( + sum, + test_struct.x as u64 + test_struct.y as u64 + test_struct.z as u64 + ); + } +} diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 746348a5bd..10e93386de 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -2,6 +2,8 @@ #![no_std] use core::fmt; +pub mod alloca; + // A list of C functions that are being imported extern "C" { pub fn printf(format: *const u8, ...) -> i32; @@ -426,3 +428,39 @@ impl Drop for RocStr { } } } + +#[allow(non_camel_case_types)] +type c_char = u8; + +#[repr(u64)] +pub enum RocCallResult { + Success(T), + Failure(*mut c_char), +} + +impl Into> for RocCallResult { + fn into(self) -> Result { + use RocCallResult::*; + + match self { + Success(value) => Ok(value), + Failure(failure) => Err({ + let msg = unsafe { + let mut null_byte_index = 0; + loop { + if *failure.offset(null_byte_index) == 0 { + break; + } + null_byte_index += 1; + } + + let bytes = core::slice::from_raw_parts(failure, null_byte_index as usize); + + core::str::from_utf8_unchecked(bytes) + }; + + msg + }), + } + } +}