kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9e883148e4350039f3af0f2552be44a84aeeaa96
parent a4454de9af46344e812fd44794a3250c6672d928
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 16 May 2026 15:44:55 -0700

Rewrite toy frontend toward spec

Diffstat:
Mdoc/TOY.md | 15+++++++++++++++
Adoc/TOY_REWRITE_TASKS.md | 275+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/objdump.c | 14+++++++++++++-
Minclude/cfree.h | 1+
Alang/toy/asm.c | 493+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/attrs.c | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/builtins.c | 1323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/compile.c | 33+++++++++++++++++++++++++++++++++
Alang/toy/data.c | 507+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/decls.c | 511+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/expr.c | 1335+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/internal.h | 378+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/lexer.c | 293+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/lexer.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/literals.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/parser.c | 1645+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/parser_core.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alang/toy/symbols.c | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dlang/toy/toy.c | 2967-------------------------------------------------------------------------------
Alang/toy/types.c | 776+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/cg.c | 34++++++++++++++++++++++------------
Msrc/api/pipeline.c | 1+
Msrc/arch/aa64/ops.c | 27+++++++++++++++++++++++++++
Msrc/obj/macho_emit.c | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/obj/macho_read.c | 34+++++++++++++++++++++++++++++++---
Mtest/api/cg_type_test.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/toy/cases/01_return_const.toy | 2+-
Mtest/toy/cases/02_arith_precedence.toy | 2+-
Mtest/toy/cases/03_bitwise_shift.toy | 2+-
Mtest/toy/cases/04_cmp_values.toy | 2+-
Mtest/toy/cases/05_if_else.toy | 4++--
Mtest/toy/cases/06_while_sum.toy | 6+++---
Mtest/toy/cases/07_break_continue.toy | 6+++---
Mtest/toy/cases/08_recursion_fib.toy | 4++--
Mtest/toy/cases/09_function_params.toy | 4++--
Atest/toy/cases/100_record_data_relocation.expected | 1+
Atest/toy/cases/100_record_data_relocation.toy | 11+++++++++++
Atest/toy/cases/101_extern_threadlocal_decls.expected | 1+
Atest/toy/cases/101_extern_threadlocal_decls.toy | 6++++++
Atest/toy/cases/102_typed_asm_operands.expected | 1+
Atest/toy/cases/102_typed_asm_operands.toy | 11+++++++++++
Atest/toy/cases/103_return_control_expressions.expected | 1+
Atest/toy/cases/103_return_control_expressions.toy | 35+++++++++++++++++++++++++++++++++++
Atest/toy/cases/104_typed_asm_clobber_abi.expected | 1+
Atest/toy/cases/104_typed_asm_clobber_abi.toy | 10++++++++++
Atest/toy/cases/105_typed_asm_record_outputs.expected | 1+
Atest/toy/cases/105_typed_asm_record_outputs.toy | 9+++++++++
Atest/toy/cases/106_forward_record_pointer_field_access.expected | 1+
Atest/toy/cases/106_forward_record_pointer_field_access.toy | 16++++++++++++++++
Atest/toy/cases/107_forward_record_pointer_field_store_call.expected | 1+
Atest/toy/cases/107_forward_record_pointer_field_store_call.toy | 21+++++++++++++++++++++
Atest/toy/cases/108_typed_asm_named_inputs.expected | 1+
Atest/toy/cases/108_typed_asm_named_inputs.toy | 10++++++++++
Atest/toy/cases/109_typed_asm_omitted_groups.expected | 1+
Atest/toy/cases/109_typed_asm_omitted_groups.toy | 4++++
Mtest/toy/cases/10_pointer_addr_deref.toy | 8++++----
Atest/toy/cases/110_typed_asm_named_outputs.expected | 1+
Atest/toy/cases/110_typed_asm_named_outputs.toy | 8++++++++
Atest/toy/cases/111_many_function_params.expected | 1+
Atest/toy/cases/111_many_function_params.toy | 14++++++++++++++
Atest/toy/cases/112_many_function_type_params.expected | 1+
Atest/toy/cases/112_many_function_type_params.toy | 19+++++++++++++++++++
Atest/toy/cases/113_many_record_fields.expected | 1+
Atest/toy/cases/113_many_record_fields.toy | 14++++++++++++++
Atest/toy/cases/114_many_tuple_fields.expected | 1+
Atest/toy/cases/114_many_tuple_fields.toy | 14++++++++++++++
Atest/toy/cases/115_many_anonymous_record_fields.expected | 1+
Atest/toy/cases/115_many_anonymous_record_fields.toy | 12++++++++++++
Atest/toy/cases/116_many_global_record_fields.expected | 1+
Atest/toy/cases/116_many_global_record_fields.toy | 15+++++++++++++++
Atest/toy/cases/117_forward_record_pointer_cast_source.toy | 12++++++++++++
Atest/toy/cases/117_many_enum_values.expected | 1+
Atest/toy/cases/117_many_enum_values.toy | 13+++++++++++++
Atest/toy/cases/118_decl_extra_attrs.toy | 26++++++++++++++++++++++++++
Atest/toy/cases/118_many_enum_switch_values.expected | 1+
Atest/toy/cases/118_many_enum_switch_values.toy | 23+++++++++++++++++++++++
Atest/toy/cases/119_static_labeladdr_data.toy | 17+++++++++++++++++
Atest/toy/cases/119_switch_strategy_hints.expected | 1+
Atest/toy/cases/119_switch_strategy_hints.toy | 12++++++++++++
Mtest/toy/cases/11_global_mutation.toy | 4++--
Atest/toy/cases/120_data_symdiff.toy | 8++++++++
Atest/toy/cases/121_dynamic_memory_builtin.expected | 1+
Atest/toy/cases/121_dynamic_memory_builtin.toy | 17+++++++++++++++++
Atest/toy/cases/122_data_entsize.expected | 1+
Atest/toy/cases/122_data_entsize.objdump | 4++++
Atest/toy/cases/122_data_entsize.toy | 7+++++++
Atest/toy/cases/123_spec_demo.expected | 1+
Atest/toy/cases/123_spec_demo.toy | 376+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/toy/cases/12_short_circuit_and_skip.toy | 8++++----
Mtest/toy/cases/13_short_circuit_or_skip.toy | 8++++----
Mtest/toy/cases/14_pointer_param.toy | 8++++----
Mtest/toy/cases/15_cg_api_types_bytes_globals.toy | 8++++----
Mtest/toy/cases/16_cg_api_memory_index.toy | 18+++++++++---------
Mtest/toy/cases/17_cg_api_atomics_intrinsics.toy | 14++++++--------
Mtest/toy/cases/18_cg_api_field_tail.toy | 16++++++++++++----
Mtest/toy/cases/19_cg_api_variadic_asm.toy | 44+++++++++++++++++++++++---------------------
Mtest/toy/cases/20_cg_api_inline_asm_full.toy | 68+++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtest/toy/cases/21_cg_api_scalar_type_queries.toy | 11++++++++---
Mtest/toy/cases/22_cg_api_typed_atomics_bits.toy | 14+++++++-------
Mtest/toy/cases/23_cg_api_typed_varargs.toy | 15++++++---------
Mtest/toy/cases/24_tail_arg_permute.toy | 6+++---
Mtest/toy/cases/25_tail_many_stack_args.toy | 10+++++-----
Mtest/toy/cases/26_tail_live_pressure.toy | 24++++++++++++------------
Mtest/toy/cases/27_tail_mixed_int_fp.toy | 8++++----
Mtest/toy/cases/28_tail_chain.toy | 8++++----
Mtest/toy/cases/29_tail_cross_arch_stack.toy | 10+++++-----
Mtest/toy/cases/30_tail_indirect_wanted.toy | 6+++---
Mtest/toy/cases/31_fn_pointer_call.toy | 8++++----
Atest/toy/cases/32_inference_null_postfix.expected | 1+
Atest/toy/cases/32_inference_null_postfix.toy | 14++++++++++++++
Atest/toy/cases/33_array_literal_index.expected | 1+
Atest/toy/cases/33_array_literal_index.toy | 4++++
Atest/toy/cases/34_record_literal_field.expected | 1+
Atest/toy/cases/34_record_literal_field.toy | 9+++++++++
Atest/toy/cases/35_string_global_data.expected | 1+
Atest/toy/cases/35_string_global_data.toy | 5+++++
Atest/toy/cases/36_tuple_record_literal.expected | 1+
Atest/toy/cases/36_tuple_record_literal.toy | 9+++++++++
Atest/toy/cases/37_enum_dot_constant.expected | 1+
Atest/toy/cases/37_enum_dot_constant.toy | 11+++++++++++
Atest/toy/cases/38_declarations_alias_extern.expected | 1+
Atest/toy/cases/38_declarations_alias_extern.toy | 15+++++++++++++++
Atest/toy/cases/39_pointer_to_array_index.expected | 1+
Atest/toy/cases/39_pointer_to_array_index.toy | 5+++++
Atest/toy/cases/40_alloca_memmove.expected | 1+
Atest/toy/cases/40_alloca_memmove.toy | 7+++++++
Atest/toy/cases/41_if_expression.expected | 1+
Atest/toy/cases/41_if_expression.toy | 8++++++++
Atest/toy/cases/42_switch_expression.expected | 1+
Atest/toy/cases/42_switch_expression.toy | 15+++++++++++++++
Atest/toy/cases/43_while_expression_break.expected | 1+
Atest/toy/cases/43_while_expression_break.toy | 13+++++++++++++
Atest/toy/cases/44_attribute_syntax.expected | 1+
Atest/toy/cases/44_attribute_syntax.toy | 5+++++
Atest/toy/cases/45_qual_addrspace_types.expected | 1+
Atest/toy/cases/45_qual_addrspace_types.toy | 5+++++
Atest/toy/cases/46_static_local.expected | 1+
Atest/toy/cases/46_static_local.toy | 9+++++++++
Atest/toy/cases/47_target_arch_switch.expected | 1+
Atest/toy/cases/47_target_arch_switch.toy | 17+++++++++++++++++
Atest/toy/cases/48_supports_callconv.expected | 1+
Atest/toy/cases/48_supports_callconv.toy | 8++++++++
Atest/toy/cases/49_typed_asm_void.expected | 1+
Atest/toy/cases/49_typed_asm_void.toy | 4++++
Atest/toy/cases/50_switch_statement.expected | 1+
Atest/toy/cases/50_switch_statement.toy | 15+++++++++++++++
Atest/toy/cases/51_labeladdr_goto.expected | 1+
Atest/toy/cases/51_labeladdr_goto.toy | 8++++++++
Atest/toy/cases/52_global_array_data.expected | 1+
Atest/toy/cases/52_global_array_data.toy | 5+++++
Atest/toy/cases/53_conversion_builtins.expected | 1+
Atest/toy/cases/53_conversion_builtins.toy | 7+++++++
Atest/toy/cases/54_scalar_intrinsics.expected | 1+
Atest/toy/cases/54_scalar_intrinsics.toy | 6++++++
Atest/toy/cases/55_block_scope_shadow.expected | 1+
Atest/toy/cases/55_block_scope_shadow.toy | 7+++++++
Atest/toy/cases/56_i128_scalar.expected | 1+
Atest/toy/cases/56_i128_scalar.toy | 3+++
Atest/toy/cases/57_string_escapes.expected | 1+
Atest/toy/cases/57_string_escapes.toy | 5+++++
Atest/toy/cases/58_overflow_record.expected | 1+
Atest/toy/cases/58_overflow_record.toy | 3+++
Atest/toy/cases/59_atomic_cmpxchg_record.expected | 1+
Atest/toy/cases/59_atomic_cmpxchg_record.toy | 5+++++
Atest/toy/cases/60_atomic_queries.expected | 1+
Atest/toy/cases/60_atomic_queries.toy | 6++++++
Atest/toy/cases/61_labeled_loop.expected | 1+
Atest/toy/cases/61_labeled_loop.toy | 10++++++++++
Atest/toy/cases/62_decl_data_attrs.expected | 1+
Atest/toy/cases/62_decl_data_attrs.objdump | 5+++++
Atest/toy/cases/62_decl_data_attrs.toy | 17+++++++++++++++++
Atest/toy/cases/63_memory_flags.expected | 1+
Atest/toy/cases/63_memory_flags.toy | 11+++++++++++
Atest/toy/cases/64_target_feature_queries.expected | 1+
Atest/toy/cases/64_target_feature_queries.toy | 11+++++++++++
Atest/toy/cases/65_rounding_conversions.expected | 1+
Atest/toy/cases/65_rounding_conversions.toy | 7+++++++
Atest/toy/cases/66_anonymous_record_type.expected | 1+
Atest/toy/cases/66_anonymous_record_type.toy | 4++++
Atest/toy/cases/67_abi_attrs.expected | 1+
Atest/toy/cases/67_abi_attrs.toy | 9+++++++++
Atest/toy/cases/68_field_attrs.expected | 1+
Atest/toy/cases/68_field_attrs.toy | 11+++++++++++
Atest/toy/cases/69_if_value_block_statements.expected | 1+
Atest/toy/cases/69_if_value_block_statements.toy | 10++++++++++
Atest/toy/cases/70_labeled_switch_break.expected | 1+
Atest/toy/cases/70_labeled_switch_break.toy | 14++++++++++++++
Atest/toy/cases/71_forward_records.expected | 1+
Atest/toy/cases/71_forward_records.toy | 18++++++++++++++++++
Atest/toy/cases/72_global_record_data.expected | 1+
Atest/toy/cases/72_global_record_data.toy | 10++++++++++
Atest/toy/cases/73_atomic_access_group.expected | 1+
Atest/toy/cases/73_atomic_access_group.toy | 5+++++
Atest/toy/cases/74_atomic_rmw_access.expected | 1+
Atest/toy/cases/74_atomic_rmw_access.toy | 5+++++
Atest/toy/cases/75_atomic_cmpxchg_access.expected | 1+
Atest/toy/cases/75_atomic_cmpxchg_access.toy | 8++++++++
Atest/toy/cases/76_atomic_query_access.expected | 1+
Atest/toy/cases/76_atomic_query_access.toy | 8++++++++
Atest/toy/cases/77_atomic_keyword_ops.expected | 1+
Atest/toy/cases/77_atomic_keyword_ops.toy | 5+++++
Atest/toy/cases/78_null_expected_pointer.expected | 1+
Atest/toy/cases/78_null_expected_pointer.toy | 7+++++++
Atest/toy/cases/79_enum_argument_context.expected | 1+
Atest/toy/cases/79_enum_argument_context.toy | 11+++++++++++
Atest/toy/cases/80_enum_switch_labels.expected | 1+
Atest/toy/cases/80_enum_switch_labels.toy | 16++++++++++++++++
Atest/toy/cases/81_global_float_data.expected | 1+
Atest/toy/cases/81_global_float_data.toy | 5+++++
Atest/toy/cases/82_unreachable_builtin.expected | 1+
Atest/toy/cases/82_unreachable_builtin.toy | 7+++++++
Atest/toy/cases/83_pointer_field_access.expected | 1+
Atest/toy/cases/83_pointer_field_access.toy | 9+++++++++
Atest/toy/cases/84_field_assignment.expected | 1+
Atest/toy/cases/84_field_assignment.toy | 9+++++++++
Atest/toy/cases/85_packed_record_attr.expected | 1+
Atest/toy/cases/85_packed_record_attr.toy | 8++++++++
Atest/toy/cases/86_address_of_field.expected | 1+
Atest/toy/cases/86_address_of_field.toy | 9+++++++++
Atest/toy/cases/87_expect_preserves_type.expected | 1+
Atest/toy/cases/87_expect_preserves_type.toy | 4++++
Atest/toy/cases/88_scalar_intrinsic_preserves_type.expected | 1+
Atest/toy/cases/88_scalar_intrinsic_preserves_type.toy | 5+++++
Atest/toy/cases/89_labeled_while_expression.expected | 1+
Atest/toy/cases/89_labeled_while_expression.toy | 13+++++++++++++
Atest/toy/cases/90_continue_through_switch.expected | 1+
Atest/toy/cases/90_continue_through_switch.toy | 16++++++++++++++++
Atest/toy/cases/91_exhaustive_enum_switch_expression.expected | 1+
Atest/toy/cases/91_exhaustive_enum_switch_expression.toy | 21+++++++++++++++++++++
Atest/toy/cases/92_parenthesized_precedence_islands.expected | 1+
Atest/toy/cases/92_parenthesized_precedence_islands.toy | 3+++
Atest/toy/cases/93_data_pad_align.expected | 1+
Atest/toy/cases/93_data_pad_align.toy | 6++++++
Atest/toy/cases/94_restrict_pointer_type.expected | 1+
Atest/toy/cases/94_restrict_pointer_type.toy | 5+++++
Atest/toy/cases/95_static_local_string_data.expected | 1+
Atest/toy/cases/95_static_local_string_data.toy | 9+++++++++
Atest/toy/cases/96_data_relocations.expected | 1+
Atest/toy/cases/96_data_relocations.toy | 8++++++++
Atest/toy/cases/97_let_pointer_pointee_assignment.expected | 1+
Atest/toy/cases/97_let_pointer_pointee_assignment.toy | 6++++++
Atest/toy/cases/98_tuple_offsetof.expected | 1+
Atest/toy/cases/98_tuple_offsetof.toy | 8++++++++
Atest/toy/cases/99_global_tuple_data.expected | 1+
Atest/toy/cases/99_global_tuple_data.toy | 10++++++++++
Mtest/toy/demo.toy | 64+++++++++++++++++++++++++++++++++-------------------------------
Atest/toy/err/common_readonly.expected | 1+
Atest/toy/err/common_readonly.toy | 1+
Atest/toy/err/common_with_initializer.expected | 1+
Atest/toy/err/common_with_initializer.toy | 1+
Atest/toy/err/compile_error_builtin.expected | 1+
Atest/toy/err/compile_error_builtin.toy | 3+++
Atest/toy/err/continue_to_switch.expected | 1+
Atest/toy/err/continue_to_switch.toy | 10++++++++++
Atest/toy/err/data_pcrel_bad_width.expected | 1+
Atest/toy/err/data_pcrel_bad_width.toy | 6++++++
Atest/toy/err/data_pcrel_outside_initializer.expected | 1+
Atest/toy/err/data_pcrel_outside_initializer.toy | 5+++++
Atest/toy/err/direct_recursive_record.expected | 1+
Atest/toy/err/direct_recursive_record.toy | 3+++
Atest/toy/err/forward_record_pointer_arg_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_arg_mismatch.toy | 11+++++++++++
Atest/toy/err/forward_record_pointer_array_literal_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_array_literal_mismatch.toy | 8++++++++
Atest/toy/err/forward_record_pointer_assign_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_assign_mismatch.toy | 9+++++++++
Atest/toy/err/forward_record_pointer_break_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_break_mismatch.toy | 12++++++++++++
Atest/toy/err/forward_record_pointer_fnptr_arg_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_fnptr_arg_mismatch.toy | 12++++++++++++
Atest/toy/err/forward_record_pointer_let_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_let_mismatch.toy | 8++++++++
Atest/toy/err/forward_record_pointer_record_literal_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_record_literal_mismatch.toy | 12++++++++++++
Atest/toy/err/forward_record_pointer_return_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_return_mismatch.toy | 11+++++++++++
Atest/toy/err/forward_record_pointer_tail_mismatch.expected | 1+
Atest/toy/err/forward_record_pointer_tail_mismatch.toy | 14++++++++++++++
Atest/toy/err/immutable_local_assign.expected | 1+
Atest/toy/err/immutable_local_assign.toy | 5+++++
Atest/toy/err/immutable_local_field_assign.expected | 1+
Atest/toy/err/immutable_local_field_assign.toy | 9+++++++++
Atest/toy/err/invalid_address_of_literal.expected | 1+
Atest/toy/err/invalid_address_of_literal.toy | 4++++
Atest/toy/err/invalid_addrspace_negative.expected | 1+
Atest/toy/err/invalid_addrspace_negative.toy | 4++++
Atest/toy/err/invalid_alias_object_attribute.expected | 1+
Atest/toy/err/invalid_alias_object_attribute.toy | 5+++++
Atest/toy/err/invalid_array_literal_too_many.expected | 1+
Atest/toy/err/invalid_array_literal_too_many.toy | 4++++
Atest/toy/err/invalid_asm_duplicate_clobber_abi.expected | 1+
Atest/toy/err/invalid_asm_duplicate_clobber_abi.toy | 9+++++++++
Atest/toy/err/invalid_asm_duplicate_clobbers.expected | 1+
Atest/toy/err/invalid_asm_duplicate_clobbers.toy | 4++++
Atest/toy/err/invalid_asm_duplicate_flags.expected | 1+
Atest/toy/err/invalid_asm_duplicate_flags.toy | 4++++
Atest/toy/err/invalid_asm_duplicate_inputs.expected | 1+
Atest/toy/err/invalid_asm_duplicate_inputs.toy | 4++++
Atest/toy/err/invalid_asm_input_constraint.expected | 1+
Atest/toy/err/invalid_asm_input_constraint.toy | 4++++
Atest/toy/err/invalid_asm_input_wrapper.expected | 1+
Atest/toy/err/invalid_asm_input_wrapper.toy | 4++++
Atest/toy/err/invalid_asm_missing_outputs.expected | 1+
Atest/toy/err/invalid_asm_missing_outputs.toy | 3+++
Atest/toy/err/invalid_asm_output_constraint.expected | 1+
Atest/toy/err/invalid_asm_output_constraint.toy | 3+++
Atest/toy/err/invalid_asm_record_output_count.expected | 1+
Atest/toy/err/invalid_asm_record_output_count.toy | 10++++++++++
Atest/toy/err/invalid_asm_record_output_name.expected | 1+
Atest/toy/err/invalid_asm_record_output_name.toy | 10++++++++++
Atest/toy/err/invalid_asm_result_type.expected | 1+
Atest/toy/err/invalid_asm_result_type.toy | 9+++++++++
Atest/toy/err/invalid_asm_unknown_clobber_abi.expected | 1+
Atest/toy/err/invalid_asm_unknown_clobber_abi.toy | 11+++++++++++
Atest/toy/err/invalid_asm_unknown_flag.expected | 1+
Atest/toy/err/invalid_asm_unknown_flag.toy | 4++++
Atest/toy/err/invalid_computed_goto_target_list.expected | 1+
Atest/toy/err/invalid_computed_goto_target_list.toy | 7+++++++
Atest/toy/err/invalid_extern_data_attribute.expected | 1+
Atest/toy/err/invalid_extern_data_attribute.toy | 1+
Atest/toy/err/invalid_fn_object_attribute.expected | 1+
Atest/toy/err/invalid_fn_object_attribute.toy | 3+++
Atest/toy/err/invalid_null_context.expected | 1+
Atest/toy/err/invalid_null_context.toy | 4++++
Atest/toy/err/invalid_object_function_attribute.expected | 1+
Atest/toy/err/invalid_object_function_attribute.toy | 5+++++
Atest/toy/err/invalid_record_literal_unknown_field.expected | 1+
Atest/toy/err/invalid_record_literal_unknown_field.toy | 8++++++++
Atest/toy/err/invalid_switch_strategy_hint.expected | 1+
Atest/toy/err/invalid_switch_strategy_hint.toy | 8++++++++
Atest/toy/err/legacy_asm_arch_selector.expected | 1+
Atest/toy/err/legacy_asm_arch_selector.toy | 4++++
Atest/toy/err/legacy_asm_builtin.expected | 1+
Atest/toy/err/legacy_asm_builtin.toy | 3+++
Atest/toy/err/legacy_asm_clobber_builtin.expected | 1+
Atest/toy/err/legacy_asm_clobber_builtin.toy | 3+++
Atest/toy/err/legacy_asm_early_builtin.expected | 1+
Atest/toy/err/legacy_asm_early_builtin.toy | 3+++
Atest/toy/err/legacy_asm_imm_builtin.expected | 1+
Atest/toy/err/legacy_asm_imm_builtin.toy | 3+++
Atest/toy/err/legacy_asm_inout_builtin.expected | 1+
Atest/toy/err/legacy_asm_inout_builtin.toy | 3+++
Atest/toy/err/legacy_asm_int_builtin.expected | 1+
Atest/toy/err/legacy_asm_int_builtin.toy | 3+++
Atest/toy/err/legacy_asm_mem_builtin.expected | 1+
Atest/toy/err/legacy_asm_mem_builtin.toy | 4++++
Atest/toy/err/legacy_asm_memory_builtin.expected | 1+
Atest/toy/err/legacy_asm_memory_builtin.toy | 3+++
Atest/toy/err/legacy_asmnop_builtin.expected | 1+
Atest/toy/err/legacy_asmnop_builtin.toy | 3+++
Atest/toy/err/legacy_atomic_builtin.expected | 1+
Atest/toy/err/legacy_atomic_builtin.toy | 5+++++
Atest/toy/err/legacy_byteconst_builtin.expected | 1+
Atest/toy/err/legacy_byteconst_builtin.toy | 3+++
Atest/toy/err/legacy_fieldtest_builtin.expected | 1+
Atest/toy/err/legacy_fieldtest_builtin.toy | 3+++
Atest/toy/err/legacy_index_builtin.expected | 1+
Atest/toy/err/legacy_index_builtin.toy | 5+++++
Atest/toy/err/legacy_int_type.expected | 1+
Atest/toy/err/legacy_int_type.toy | 3+++
Atest/toy/err/legacy_logical_operator.expected | 1+
Atest/toy/err/legacy_logical_operator.toy | 6++++++
Atest/toy/err/legacy_prefix_deref_assign.expected | 1+
Atest/toy/err/legacy_prefix_deref_assign.toy | 6++++++
Atest/toy/err/legacy_prefix_deref_expr.expected | 1+
Atest/toy/err/legacy_prefix_deref_expr.toy | 5+++++
Atest/toy/err/legacy_target_builtin.expected | 1+
Atest/toy/err/legacy_target_builtin.toy | 3+++
Atest/toy/err/legacy_target_os_builtin.expected | 1+
Atest/toy/err/legacy_target_os_builtin.toy | 3+++
Atest/toy/err/legacy_typecheck_builtin.expected | 1+
Atest/toy/err/legacy_typecheck_builtin.toy | 3+++
Atest/toy/err/legacy_va_arg_builtin.expected | 1+
Atest/toy/err/legacy_va_arg_builtin.toy | 7+++++++
Atest/toy/err/mixed_precedence_islands.expected | 1+
Atest/toy/err/mixed_precedence_islands.toy | 4++++
Atest/toy/err/mutual_by_value_record.expected | 1+
Atest/toy/err/mutual_by_value_record.toy | 14++++++++++++++
Atest/toy/err/nonexhaustive_enum_switch_expression.expected | 1+
Atest/toy/err/nonexhaustive_enum_switch_expression.toy | 14++++++++++++++
Atest/toy/err/restrict_non_pointer.expected | 1+
Atest/toy/err/restrict_non_pointer.toy | 4++++
Atest/toy/err/static_local_scope.expected | 1+
Atest/toy/err/static_local_scope.toy | 6++++++
Atest/toy/err/syntax_missing_block_close.expected | 1+
Atest/toy/err/syntax_missing_block_close.toy | 4++++
Atest/toy/err/tail_return_mismatch.expected | 1+
Atest/toy/err/tail_return_mismatch.toy | 7+++++++
Atest/toy/err/tail_variadic.expected | 1+
Atest/toy/err/tail_variadic.toy | 7+++++++
Atest/toy/err/type_mismatch.expected | 1+
Atest/toy/err/type_mismatch.toy | 3+++
Atest/toy/err/unknown_decl_attribute.expected | 1+
Atest/toy/err/unknown_decl_attribute.toy | 4++++
Atest/toy/err/unknown_enum_value.expected | 1+
Atest/toy/err/unknown_enum_value.toy | 8++++++++
Atest/toy/err/unknown_field_attribute.expected | 1+
Atest/toy/err/unknown_field_attribute.toy | 7+++++++
Atest/toy/err/unknown_record_attribute.expected | 1+
Atest/toy/err/unknown_record_attribute.toy | 7+++++++
Atest/toy/err/unsupported_coro_switch.expected | 1+
Atest/toy/err/unsupported_coro_switch.toy | 5+++++
Atest/toy/err/unsupported_cpu_nop.expected | 1+
Atest/toy/err/unsupported_cpu_nop.toy | 4++++
Atest/toy/err/unsupported_dcache_clean.expected | 1+
Atest/toy/err/unsupported_dcache_clean.toy | 5+++++
Atest/toy/err/unsupported_dcache_clean_invalidate.expected | 1+
Atest/toy/err/unsupported_dcache_clean_invalidate.toy | 5+++++
Atest/toy/err/unsupported_dcache_invalidate.expected | 1+
Atest/toy/err/unsupported_dcache_invalidate.toy | 5+++++
Atest/toy/err/unsupported_dmb.expected | 1+
Atest/toy/err/unsupported_dmb.toy | 4++++
Atest/toy/err/unsupported_dsb.expected | 1+
Atest/toy/err/unsupported_dsb.toy | 4++++
Atest/toy/err/unsupported_icache_invalidate.expected | 1+
Atest/toy/err/unsupported_icache_invalidate.toy | 5+++++
Atest/toy/err/unsupported_irq_restore.expected | 1+
Atest/toy/err/unsupported_irq_restore.toy | 4++++
Atest/toy/err/unsupported_irq_save.expected | 1+
Atest/toy/err/unsupported_irq_save.toy | 4++++
Atest/toy/err/unsupported_longjmp.expected | 1+
Atest/toy/err/unsupported_longjmp.toy | 5+++++
Atest/toy/err/unsupported_setjmp.expected | 1+
Atest/toy/err/unsupported_setjmp.toy | 4++++
Atest/toy/err/unsupported_sev.expected | 1+
Atest/toy/err/unsupported_sev.toy | 4++++
Atest/toy/err/unsupported_syscall.expected | 1+
Atest/toy/err/unsupported_syscall.toy | 3+++
Atest/toy/err/unsupported_wfe.expected | 1+
Atest/toy/err/unsupported_wfe.toy | 4++++
Atest/toy/err/unsupported_wfi.expected | 1+
Atest/toy/err/unsupported_wfi.toy | 4++++
Atest/toy/err/use_function_before_decl.expected | 1+
Atest/toy/err/use_function_before_decl.toy | 7+++++++
Atest/toy/err/use_type_before_decl.expected | 1+
Atest/toy/err/use_type_before_decl.toy | 9+++++++++
Mtest/toy/run.sh | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
437 files changed, 11153 insertions(+), 3174 deletions(-)

diff --git a/doc/TOY.md b/doc/TOY.md @@ -32,6 +32,21 @@ Declarations must appear before use unless a declaration form explicitly defines an external symbol. Source names use C linkage spelling for object-file symbols. +## Runnable Demo + +A broad executable demo lives in `test/toy/cases/123_spec_demo.toy`. It is part +of `make test-toy` and is intentionally written as a normal corpus case rather +than a documentation-only sample, so the implementation must keep accepting and +executing the specified syntax it demonstrates. + +The demo covers aliases, records, packed/aligned fields, tuple records, enums, +extern declarations, public symbols, aliases, global and static data, recursive +record pointers, function pointers, tail calls, varargs, ABI attributes, +aggregate literals, field and index lvalues, expression `if`/`switch`/`while`, +labeled loops, computed goto, memory builtins, atomics, scalar intrinsics, +conversion builtins, target queries, inline assembly, address-space pointers, +and nullable pointer casts. + ## Lexical Conventions Identifiers are ASCII names beginning with a letter or `_`, followed by letters, diff --git a/doc/TOY_REWRITE_TASKS.md b/doc/TOY_REWRITE_TASKS.md @@ -0,0 +1,275 @@ +# Toy Rewrite Task List + +This tracks the implementation rewrite toward `doc/TOY.md`. Work proceeds +red-green: rewrite or add tests first, run focused failures, then implement the +smallest slice that moves those tests green. + +Completion rule: this task list is not a partial-coverage plan. Future agents +must continue until `lang/toy` is fully aligned with `doc/TOY.md`, including +internal refactors, representation cleanup, stronger diagnostics, and removal +of temporary shortcuts. A green `make test-toy` is required after each slice, +but it is not by itself proof that the language implementation is complete. + +## Phase 1: Spec-shaped Existing Coverage + +- [x] Rewrite existing runnable toy cases from legacy `int` to explicit scalar + types, initially using `i64` where old `int` behavior was 64-bit. +- [x] Replace legacy logical operators `&&` and `||` with `and` and `or`. +- [x] Replace legacy prefix dereference `*p` with postfix `p.*` in expression + and assignment contexts. +- [x] Prefix public CG coverage builtins with `@`, including type-query, + memory, atomic, vararg, intrinsic, target, and asm helpers. +- [x] Replace legacy helper names with spec spellings where a direct mapping + exists: + `index(p, i)` -> `p[i]`, `sizeof<T>()` -> `@sizeof<T>()`, + `alignof<T>()` -> `@alignof<T>()`, `offsetof<T>(f)` -> + `@offsetof<T>(f)`. +- [x] Keep expected exit codes unchanged unless a test intentionally changes + semantics. + +## Phase 2: Focused Red Tests + +- [x] Add a first-class byte string/global data initializer case. +- [x] Add array literal and indexing coverage. +- [x] Add pointer-to-array address behavior cases. +- [x] Add `let name = expr` inference coverage. +- [x] Add `var name = expr` inference coverage. +- [x] Add `NULL as *T` pointer literal coverage. +- [x] Add record declaration, record literal, omitted field zero-fill, and field + projection coverage. +- [x] Add tuple record literal and numeric field projection coverage. +- [x] Add enum declaration and dot-constant typed initializer coverage. +- [x] Add `pub`, `extern`, and `alias` declaration coverage. +- [x] Add one error-test harness pass for compile-fail diagnostics before adding + many negative parser/type cases. + +## Phase 3: Frontend Structure + +- [x] Split the previous single `toy.c` implementation into explicit frontend + modules: public compile entry, lexer, parser core/context, symbol/scope + tables, literal helpers, and parser implementation. +- [x] Introduce a Toy type layer that can represent aliases, nominal records, + tuple records, enums, arrays, pointers, function pointers, qualifiers, and + anonymous records while lowering through public CG API types. +- [x] Replace fixed-size global/local/function arrays with context-owned + growable storage; `lang/toy` stays on the public frontend boundary, so + this uses explicit `ToyParser` ownership rather than internal core vector + headers. +- [x] Add lexical scopes for block-local declarations without global state. +- [x] Keep codegen target and ABI details hanging off `ToyParser`/future Toy + context structures. +- [x] Remove legacy compatibility spellings and temporary lowering shortcuts + once spec-shaped replacements have full coverage. +- [x] Add focused negative tests for every rejected spec form and unsupported + backend feature path. + +## Phase 4: Spec Features + +- [x] Declarations: `pub`, `extern`, attributes, thread-local objects, alias, + readonly/mutable object definitions, function-local statics. +- [x] Types: full scalar set, address-space pointers, arrays, function types, + qualifiers, aliases, records, tuple records, anonymous records, enums. +- [x] Expressions: `NULL`, byte strings, casts, postfix calls/index/field/deref, + type-safe lvalues, precedence-island restrictions, aggregate literals. +- [x] Statements: assignment-only lvalues, expression statements, labels, + labelled loops/switches, value-bearing `break`, `return tail`. +- [x] Expression control flow: `if` expressions, result-typed `while<T> else`, + expression switches. +- [x] Builtins: varargs, type queries, memory, data relocations, atomics, + intrinsics, target capability queries, and typed inline assembly. +- [x] Error tests: syntax, type mismatch, declaration order, unsupported target + intrinsics, invalid attributes, direct recursive records, invalid tail + calls, and invalid builtin forms. + +## Remaining Work To Reach `doc/TOY.md` + +The previous rewrite checklist is complete, but `doc/TOY.md` still describes +several behaviors that the implementation does not yet provide. These are now +tracked as red tests first; keep the tests aligned with the spec and make the +implementation catch up in focused green slices. + +- [x] Implement function-local static object initializers containing + `@labeladdr(label)` while the containing function is open. The positive + coverage is `test/toy/cases/119_static_labeladdr_data.toy`. +- [x] Implement `@symdiff(lhs, rhs, addend)` object initializers for every + object format/target where `doc/TOY.md` exposes the builtin, or narrow the + spec if the portability contract changes. The positive coverage is + `test/toy/cases/120_data_symdiff.toy`. +- [x] Expose dynamic `@memcpy`, `@memmove`, and `@memset` lowering through Toy + by accepting expression-valued `size` and `align` operands, not only + numeric literals. The positive coverage starts with + `test/toy/cases/121_dynamic_memory_builtin.toy`. +- [x] Propagate `.entsize(N)` from Toy data-definition attributes through + `CfreeCgDataDefAttrs` into object sections, including merge/string + sections. The red API assertion lives in `test/api/cg_type_test.c`. +- [x] Add object-format inspection coverage for Toy `.entsize(N)` once the + public object inspection surface can assert section entry size directly + without relying on textual objdump details. + +## Current Slice + +- [x] Convert legacy runnable tests to spec syntax. +- [x] Add minimal new syntax tests for `@` builtins, `and`/`or`, postfix + dereference, inference, and `NULL`. +- [x] Implement only enough lexer/parser/codegen support to make the converted + core tests pass. +- [x] Add object-inspection checks for toy cases and lower declaration/data + attributes for weak symbols, aliases, object alignment, readonly storage, + and common definitions. +- [x] Extend memory-operation parsing for `@memcpy`, `@memmove`, and + `@memset` access flags, and cover address-of indexed lvalues. +- [x] Add target capability query builtins for symbol and backend features. +- [x] Add explicit rounding-mode conversion builtins for int/float edges. +- [x] Parse anonymous `record { ... }` type literals and use the existing + aggregate initializer/projection lowering for locals. +- [x] Parse inline ABI attributes on function parameters and return types. +- [x] Parse record field attributes for alignment/packed field layout. +- [x] Allow value blocks for expression control flow to contain preceding + statements before the final unsuffixed expression. +- [x] Give statement switches control scopes and support labelled switch + breaks. +- [x] Support forward record declarations and unresolved pointer fields using + erased pointer storage. +- [x] Emit top-level record data definitions from constant named-field + initializers. +- [x] Parse atomic `access(...)` groups for typed load/store/RMW/cmpxchg + operations. +- [x] Parse atomic `access(...)` groups for legality and lock-free queries. +- [x] Accept keyword-shaped dot constants for atomic RMW operations. +- [x] Use typed local initializer context for `NULL` pointer literals. +- [x] Use function parameter context to resolve enum dot constants in calls. +- [x] Resolve enum dot constants in switch arm labels from selector type. +- [x] Emit floating-point top-level object initializers with + `cfree_cg_data_float`. +- [x] Cover `@unreachable()` as a statement-position terminator builtin. +- [x] Route no-argument low-level intrinsics through CG and cover unsupported + target intrinsic diagnostics with an error test. +- [x] Implement implicit dereference for field access on `*Record`. +- [x] Support field assignment lvalues for records. +- [x] Lower record `.packed` and `.align(N)` layout attributes through record + type construction. +- [x] Support address-of field lvalues. +- [x] Preserve operand type for `@expect`. +- [x] Preserve operand type for integer scalar intrinsics. +- [x] Add negative coverage for direct by-value recursive records. +- [x] Support labelled result-typed while expressions. +- [x] Distinguish loop and switch control scopes so `continue` can only target + loops, including unlabeled `continue` through nested switches. +- [x] Allow expression switches over enums to omit `default` only when every + enum value is covered. +- [x] Enforce precedence-island boundaries while allowing parenthesized mixed + islands and normal additive/multiplicative precedence. +- [x] Reject legacy `int`, `&&`, and `||` spellings now that spec-shaped tests + use explicit scalar types and `and`/`or`. +- [x] Lower `@labeladdr` and computed `goto` through the public CG label-address + API, with a CG-level compare-chain fallback for targets without native + label-address branches. +- [x] Support `@pad` and `@align` low-level data initializer builtins in + top-level byte-array object definitions. +- [x] Reject `restrict` qualifiers on non-pointer types while preserving valid + pointer-qualified type parsing. +- [x] Replace accidental direct-recursive-record failures with an explicit + incomplete-record-by-value diagnostic. +- [x] Keep function-local statics in lexical local scope by representing them + as scoped symbol-backed variables instead of global source bindings. +- [x] Support byte-string initializers for function-local static byte arrays. +- [x] Structurally split `lang/toy` into `compile.c`, `lexer.c/.h`, + `internal.h`, `parser_core.c`, `symbols.c`, `literals.c`, and + `parser.c` while preserving the public `cfree_toy_compile` API. +- [x] Replace fixed parser-context arrays for locals, functions, globals, + named types, scopes, and labels with growable `ToyParser`-owned storage + and explicit parser cleanup. +- [x] Split type inspection and named-type registration into `types.c`, with a + `ToyTypeTable` owned by `ToyParser` instead of loose named-type arrays. +- [x] Split declaration, ABI, field, and record attribute parsing into + `attrs.c` while preserving focused coverage for attribute-heavy cases. +- [x] Add `@pcrel` lowering for typed top-level array initializers and keep + `@symdiff` parsed for supported relocation paths; Mach-O object emission + for `@symdiff` remains a backend-format follow-up. +- [x] Remove legacy untyped atomic, `@index`, prefix-deref, and old + `@va_arg(ap, T)` compatibility spellings, with negative tests for each. +- [x] Remove legacy `@target()` alias in favor of `@target_arch()`, with + negative coverage. +- [x] Track mutability on `ToyVar` and reject assignment to block-local `let` + storage while preserving mutation through pointer pointees. +- [x] Accept tuple field indexes in `@offsetof<T>(N)`. +- [x] Emit top-level tuple record data definitions from positional constant + initializers. +- [x] Add negative coverage for invalid tail calls, including variadic callees + and return-type mismatches. +- [x] Add declaration-order negative coverage for function and type use before + declaration. +- [x] Add invalid declaration, field, and record attribute negative coverage. +- [x] Support `@pcrel` data initializer builtins in typed top-level record and + tuple integer fields as well as arrays. +- [x] Add negative coverage for mutually recursive by-value records through + forward declarations. +- [x] Add declaration coverage for `extern let` and extern thread-local object + attributes. +- [x] Add data initializer negative coverage for `@pcrel` outside initializer + context and invalid pcrel slot widths. +- [x] Add a Toy-owned type metadata layer with `ToyTypeId`, structural type + entries for builtins/arrays/pointers/functions/anonymous records, + nominal entries for aliases/records/tuples/enums, qualifier entries, and + symbol-table links from locals/functions/globals/named types while + preserving existing public-CG lowering. +- [x] Move local/static-local/function/global insertion behind `symbols.c` + helpers and move named-type lookup into `types.c` so parser code stops + owning those table mutation details directly. +- [x] Replace computed-goto target fixed scratch storage with parser-owned + growable scratch space, removing the remaining fixed label-target cap. +- [x] Start replacing temporary inline-asm helpers with spec-shaped typed + `@asm<T>` parsing for `outputs(...)`, `inputs(...)`, `clobbers(...)`, + and `flags(...)`, including a runnable one-output/two-input operand case. +- [x] Rewrite the `@asm_int` coverage to spec-shaped typed `@asm<i64>` and + remove the temporary `@asm_int` builtin with negative legacy coverage. +- [x] Rewrite the `@asm_imm` coverage to typed `@asm<i64>` immediate inputs + and remove the temporary `@asm_imm` builtin with negative legacy + coverage. +- [x] Rewrite the `@asm_memory` and `@asm_clobber` coverage to typed + `@asm<void>` with `clobbers(...)`, and remove those temporary helpers + with negative legacy coverage. +- [x] Extend typed `@asm<T>` operands for memory inputs, inout outputs, and + early-clobber constraints; rewrite and remove the remaining temporary + `@asm_mem`, `@asm_inout`, and `@asm_early` helpers with negative legacy + coverage. +- [x] Rewrite the remaining legacy non-generic `@asm(...)`/`@asmnop()` + call sites to `@asm<void>` and remove those temporary parser branches + with negative legacy coverage. +- [x] Rewrite the remaining `@typecheck`, `@byteconst`, and `@fieldtest` + helper call sites to ordinary Toy constants/records and remove those + temporary builtins with negative legacy coverage. +- [x] Replace the implicit built-in `Pair` test type with an ordinary record + declaration and remove the parser/context special case for `Pair`. +- [x] Remove temporary `@target_os()` by fixing Apple ARM64 vararg `va_list` + lowering in the backend and running Toy vararg tests unconditionally, + with negative legacy coverage for `@target_os`. +- [x] Support returning expression-control-flow values directly from + `return if`, `return switch`, `return while<T>`, and labelled + result-typed `return label: while<T>` by reusing the typed + value-to-local lowering path. +- [x] Parse typed inline-assembly `clobber_abi(.caller_saved)` groups and + pass them through to `CfreeCgInlineAsm.clobber_abi_sets`. +- [x] Support typed inline-assembly record results by validating record fields + against multiple outputs and materializing those outputs into an + aggregate value for normal field projection. +- [x] Split typed inline-assembly parsing and validation into `lang/toy/asm.c` + so the main parser no longer owns that low-level builtin subgrammar. +- [x] Add negative coverage for typed inline-assembly missing outputs, result + type mismatches, record output count/name mismatches, unknown flags, and + unknown ABI clobber sets. +- [x] Move type parsing into `lang/toy/types.c` beside the Toy type metadata + layer, leaving `parser.c` focused on expressions, statements, and + declarations. +- [x] Preserve source field types for named records separately from erased CG + storage, so forward-declared record pointer fields can be initialized, + assigned, projected, and passed to typed functions after completion. +- [x] Remove the temporary inline-asm `arch(...)` template/clobber selector in + favor of ordinary typed asm strings and `@target_arch()`-based language + tests, with negative coverage for the removed selector. +- [x] Parse typed inline-assembly named input operands of the form + `name = in("constraint", expr)`. +- [x] Split Toy parsing into focused builtin (`builtins.c`), expression/lvalue + (`expr.c`), declaration (`decls.c`), data initializer (`data.c`), and + statement/control-flow (`parser.c`) modules, with shared helper + boundaries declared in `internal.h`. diff --git a/driver/objdump.c b/driver/objdump.c @@ -1,5 +1,7 @@ #include "driver.h" +#include <stdio.h> + /* `cfree objdump` — print section/symbol info, disassembly, hex contents, * and relocations for object files and archives. Archives are auto-detected * by magic; each member is dumped in turn. All display logic lives here; @@ -161,7 +163,7 @@ static int j_match(const ObjdumpOpts* o, const char* name) { static void render_sec_flags(const CfreeObjSecInfo* sec, char* buf, size_t cap) { size_t n = 0; - const char* tags[8]; + const char* tags[12]; int nt = 0; int i; int is_bss = (sec->kind == CFREE_SEC_BSS); @@ -176,6 +178,8 @@ static void render_sec_flags(const CfreeObjSecInfo* sec, char* buf, (sec->flags & CFREE_SF_ALLOC)) tags[nt++] = "DATA"; if (sec->flags & CFREE_SF_TLS) tags[nt++] = "TLS"; + if (sec->flags & CFREE_SF_MERGE) tags[nt++] = "MERGE"; + if (sec->flags & CFREE_SF_STRINGS) tags[nt++] = "STRINGS"; if (sec->kind == CFREE_SEC_DEBUG) tags[nt++] = "DEBUGGING"; for (i = 0; i < nt && n + 1 < cap; ++i) { @@ -183,6 +187,14 @@ static void render_sec_flags(const CfreeObjSecInfo* sec, char* buf, if (i > 0 && n + 1 < cap) buf[n++] = ','; while (*t && n + 1 < cap) buf[n++] = *t++; } + if (sec->entsize && n + 1 < cap) { + char tmp[32]; + int k = snprintf(tmp, sizeof tmp, "%sentsize=%u", n ? "," : "", + sec->entsize); + size_t j; + for (j = 0; k > 0 && j < (size_t)k && n + 1 < cap; ++j) + buf[n++] = tmp[j]; + } buf[n] = '\0'; } diff --git a/include/cfree.h b/include/cfree.h @@ -1323,6 +1323,7 @@ typedef struct CfreeObjSecInfo { uint32_t align; /* always a power of 2; 1 means no constraint; * 0 is reserved and never appears (ELF's "0 or 1 * means none" is normalized to 1 on read). */ + uint32_t entsize; /* section entry size, or 0 when not specified */ } CfreeObjSecInfo; typedef struct CfreeObjSymInfo { diff --git a/lang/toy/asm.c b/lang/toy/asm.c @@ -0,0 +1,493 @@ +#include "internal.h" + +#include <stdlib.h> +#include <string.h> + +typedef struct ToyAsmOperandList { + CfreeCgAsmOperand* items; + uint32_t count; + size_t cap; +} ToyAsmOperandList; + +typedef struct ToyAsmClobberList { + CfreeSym* items; + uint32_t count; + size_t cap; +} ToyAsmClobberList; + +static int toy_expect_ident(ToyParser* p, const char* name) { + if (p->cur.kind != TOK_IDENT || !toy_sym_is(p, toy_tok_sym(p, p->cur), name)) + return 0; + toy_parser_advance(p); + return 1; +} + +static int toy_asm_append_operand(ToyParser* p, ToyAsmOperandList* list, + const CfreeCgAsmOperand* operand) { + if (!toy_parser_reserve(p, (void**)&list->items, &list->cap, + (size_t)list->count + 1u, sizeof *list->items, + "asm operands")) { + return 0; + } + list->items[list->count++] = *operand; + return 1; +} + +static int toy_asm_append_clobber(ToyParser* p, ToyAsmClobberList* list, + CfreeSym clobber) { + if (!toy_parser_reserve(p, (void**)&list->items, &list->cap, + (size_t)list->count + 1u, sizeof *list->items, + "asm clobbers")) { + return 0; + } + list->items[list->count++] = clobber; + return 1; +} + +static const char* toy_asm_constraint_body(const char* s) { + if (!s) return ""; + if (s[0] == '=' && s[1] == '&') return s + 2; + if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; + return s; +} + +static int toy_asm_is_decimal_constraint(const char* s) { + if (!s || s[0] < '0' || s[0] > '9') return 0; + while (*s) { + if (*s < '0' || *s > '9') return 0; + ++s; + } + return 1; +} + +static int toy_validate_asm_output_constraint(ToyParser* p, + const CfreeCgAsmOperand* op) { + const char* s = cfree_sym_str(p->c, op->constraint, NULL); + const char* body = toy_asm_constraint_body(s); + if (op->dir == CFREE_CG_ASM_OUT) { + if (!s || s[0] != '=' || body[0] != 'r' || body[1] != '\0') { + toy_error(p, p->cur.loc, "unsupported asm output constraint"); + return 0; + } + } else if (op->dir == CFREE_CG_ASM_INOUT) { + if (!s || s[0] != '+' || body[0] != 'r' || body[1] != '\0') { + toy_error(p, p->cur.loc, "unsupported asm output constraint"); + return 0; + } + } + return 1; +} + +static int toy_validate_asm_input_constraint(ToyParser* p, + const CfreeCgAsmOperand* op) { + const char* s = cfree_sym_str(p->c, op->constraint, NULL); + if ((s && s[0] && !s[1] && + (s[0] == 'r' || s[0] == 'i' || s[0] == 'm')) || + toy_asm_is_decimal_constraint(s)) { + return 1; + } + toy_error(p, p->cur.loc, "unsupported asm input constraint"); + return 0; +} + +static int toy_parse_asm_output_operand(ToyParser* p, + CfreeCgAsmOperand* operand) { + CfreeSym op_name; + memset(operand, 0, sizeof *operand); + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected asm output operand"); + return 0; + } + op_name = toy_tok_sym(p, p->cur); + if (!toy_sym_is(p, op_name, "out") && !toy_sym_is(p, op_name, "inout")) { + operand->name = op_name; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_EQ)) { + toy_error(p, p->cur.loc, "expected '=' after asm output name"); + return 0; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected asm output operand"); + return 0; + } + op_name = toy_tok_sym(p, p->cur); + if (!toy_sym_is(p, op_name, "out") && !toy_sym_is(p, op_name, "inout")) { + toy_error(p, p->cur.loc, "expected asm output operand"); + return 0; + } + } + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_string_sym(p, &operand->constraint, NULL) || + !toy_expect_comma(p)) { + return 0; + } + if (toy_sym_is(p, op_name, "inout")) { + operand->type = toy_parse_expr(p); + if (operand->type == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) { + return 0; + } + operand->dir = CFREE_CG_ASM_INOUT; + return 1; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected asm output name"); + return 0; + } + { + CfreeSym inner_name = toy_tok_sym(p, p->cur); + if (operand->name && operand->name != inner_name) { + toy_error(p, p->cur.loc, "asm output name mismatch"); + return 0; + } + operand->name = inner_name; + } + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' in asm output"); + return 0; + } + operand->type = toy_parse_type(p); + if (operand->type == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) { + return 0; + } + operand->dir = CFREE_CG_ASM_OUT; + return 1; +} + +static int toy_parse_asm_input_operand(ToyParser* p, + CfreeCgAsmOperand* operand) { + CfreeSym mem_constraint; + memset(operand, 0, sizeof *operand); + if (p->cur.kind == TOK_IDENT && + !toy_sym_is(p, toy_tok_sym(p, p->cur), "in")) { + if (toy_lexer_peek(&p->lex).kind != TOK_EQ) { + toy_error(p, p->cur.loc, "expected asm input operand"); + return 0; + } + operand->name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_EQ)) { + toy_error(p, p->cur.loc, "expected '=' after asm input name"); + return 0; + } + } + if (!toy_expect_ident(p, "in") || !toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_string_sym(p, &operand->constraint, NULL) || + !toy_expect_comma(p)) { + toy_error(p, p->cur.loc, "expected asm input operand"); + return 0; + } + mem_constraint = cfree_sym_intern(p->c, "m"); + if (operand->constraint == mem_constraint && p->cur.kind == TOK_IDENT) { + CfreeSym name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + operand->type = toy_emit_var_lvalue(p, name); + if (operand->type == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "unknown asm memory operand"); + return 0; + } + } else { + operand->type = toy_parse_expr(p); + } + if (operand->type == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) { + return 0; + } + operand->dir = CFREE_CG_ASM_IN; + return 1; +} + +static int toy_parse_asm_outputs(ToyParser* p, ToyAsmOperandList* outputs) { + if (!toy_expect_ident(p, "outputs") || !toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected outputs(...)"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeCgAsmOperand operand; + if (!toy_parse_asm_output_operand(p, &operand) || + !toy_validate_asm_output_constraint(p, &operand) || + !toy_asm_append_operand(p, outputs, &operand)) { + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm outputs"); + return 0; + } + return 1; +} + +static int toy_parse_asm_inputs(ToyParser* p, ToyAsmOperandList* inputs) { + if (!toy_expect_ident(p, "inputs") || !toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected inputs(...)"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeCgAsmOperand operand; + if (!toy_parse_asm_input_operand(p, &operand) || + !toy_validate_asm_input_constraint(p, &operand) || + !toy_asm_append_operand(p, inputs, &operand)) { + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm inputs"); + return 0; + } + return 1; +} + +static int toy_parse_asm_clobbers(ToyParser* p, ToyAsmClobberList* clobbers) { + if (!toy_expect_ident(p, "clobbers") || !toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected clobbers(...)"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeSym clobber; + size_t len; + if (!toy_parse_arch_string(p, &clobber, &len) || + !toy_asm_append_clobber(p, clobbers, clobber)) { + return 0; + } + (void)len; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm clobbers"); + return 0; + } + return 1; +} + +static int toy_asm_record_field_by_name(ToyParser* p, CfreeCgTypeId record_ty, + CfreeSym name, uint32_t* index_out, + CfreeCgField* field_out) { + uint32_t i, nfields = cfree_cg_type_record_nfields(p->c, record_ty); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, record_ty, i, &field, NULL) == 0 && + field.name == name) { + if (index_out) *index_out = i; + if (field_out) *field_out = field; + return 1; + } + } + return 0; +} + +static int toy_parse_asm_flags(ToyParser* p, uint32_t* flags) { + if (!toy_expect_ident(p, "flags") || !toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected flags(...)"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "volatile")) + *flags |= CFREE_CG_ASM_VOLATILE; + else if (toy_sym_is(p, name, "pure")) + *flags |= CFREE_CG_ASM_PURE; + else if (toy_sym_is(p, name, "nomem")) + *flags |= CFREE_CG_ASM_NOMEM; + else if (toy_sym_is(p, name, "readonly")) + *flags |= CFREE_CG_ASM_READONLY; + else if (toy_sym_is(p, name, "preserves_flags")) + *flags |= CFREE_CG_ASM_PRESERVES_FLAGS; + else if (toy_sym_is(p, name, "nostack")) + *flags |= CFREE_CG_ASM_NOSTACK; + else if (toy_sym_is(p, name, "noreturn")) + *flags |= CFREE_CG_ASM_NORETURN; + else { + toy_error(p, p->cur.loc, "unknown asm flag"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm flags"); + return 0; + } + return 1; +} + +static int toy_parse_asm_clobber_abi(ToyParser* p, + uint32_t* clobber_abi_sets) { + if (!toy_expect_ident(p, "clobber_abi") || + !toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected clobber_abi(...)"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "caller_saved")) + *clobber_abi_sets |= CFREE_CG_ASM_CLOBBER_ABI_CALLER_SAVED; + else { + toy_error(p, p->cur.loc, "unknown asm clobber ABI"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm clobber ABI"); + return 0; + } + return 1; +} + +int toy_parse_typed_asm_tail(ToyParser* p, CfreeCgTypeId result_ty, + CfreeSym tmpl, size_t tmpl_len) { + ToyAsmOperandList outputs = {0}; + ToyAsmOperandList inputs = {0}; + ToyAsmClobberList clobbers = {0}; + uint32_t* record_field_indexes = NULL; + uint32_t flags = 0; + uint32_t clobber_abi_sets = 0; + int have_outputs = 0; + int have_inputs = 0; + int have_clobbers = 0; + int have_flags = 0; + int have_clobber_abi = 0; + int ok = 0; + + while (toy_parser_match(p, TOK_COMMA)) { + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected asm operand group"); + goto done; + } + if (toy_sym_is(p, toy_tok_sym(p, p->cur), "outputs")) { + if (have_outputs) { + toy_error(p, p->cur.loc, "duplicate asm outputs group"); + goto done; + } + if (!toy_parse_asm_outputs(p, &outputs)) goto done; + have_outputs = 1; + } else if (toy_sym_is(p, toy_tok_sym(p, p->cur), "inputs")) { + if (have_inputs) { + toy_error(p, p->cur.loc, "duplicate asm inputs group"); + goto done; + } + if (!toy_parse_asm_inputs(p, &inputs)) goto done; + have_inputs = 1; + } else if (toy_sym_is(p, toy_tok_sym(p, p->cur), "clobbers")) { + if (have_clobbers) { + toy_error(p, p->cur.loc, "duplicate asm clobbers group"); + goto done; + } + if (!toy_parse_asm_clobbers(p, &clobbers)) goto done; + have_clobbers = 1; + } else if (toy_sym_is(p, toy_tok_sym(p, p->cur), "flags")) { + if (have_flags) { + toy_error(p, p->cur.loc, "duplicate asm flags group"); + goto done; + } + if (!toy_parse_asm_flags(p, &flags)) goto done; + have_flags = 1; + } else if (toy_sym_is(p, toy_tok_sym(p, p->cur), "clobber_abi")) { + if (have_clobber_abi) { + toy_error(p, p->cur.loc, "duplicate asm clobber_abi group"); + goto done; + } + if (!toy_parse_asm_clobber_abi(p, &clobber_abi_sets)) goto done; + have_clobber_abi = 1; + } else { + toy_error(p, p->cur.loc, "unknown asm operand group"); + goto done; + } + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after asm"); + goto done; + } + if (!have_outputs) { + toy_error(p, p->cur.loc, "asm outputs group is required"); + goto done; + } + if (result_ty == toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { + if (outputs.count != 0) { + toy_error(p, p->cur.loc, "void asm cannot have outputs"); + goto done; + } + } else if (cfree_cg_type_kind(p->c, result_ty) == CFREE_CG_TYPE_RECORD) { + uint32_t i, nfields = cfree_cg_type_record_nfields(p->c, result_ty); + uint8_t* seen; + if (outputs.count != nfields) { + toy_error(p, p->cur.loc, "asm record result output count mismatch"); + goto done; + } + record_field_indexes = (uint32_t*)calloc(nfields, sizeof *record_field_indexes); + seen = (uint8_t*)calloc(nfields, sizeof *seen); + if (!record_field_indexes || !seen) { + free(seen); + toy_error(p, p->cur.loc, "out of memory growing asm record outputs"); + goto done; + } + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + uint32_t field_index = i; + if (outputs.items[i].name) { + if (!toy_asm_record_field_by_name(p, result_ty, outputs.items[i].name, + &field_index, &field)) { + free(seen); + toy_error(p, p->cur.loc, "asm record result output mismatch"); + goto done; + } + } else if (cfree_cg_type_record_field(p->c, result_ty, i, &field, + NULL) != 0) { + free(seen); + goto done; + } + if (outputs.items[i].type != field.type || seen[field_index]) { + free(seen); + toy_error(p, p->cur.loc, "asm record result output mismatch"); + goto done; + } + seen[field_index] = 1u; + record_field_indexes[i] = field_index; + } + free(seen); + } else { + if (outputs.count != 1 || outputs.items[0].type != result_ty) { + toy_error(p, p->cur.loc, "asm result type must match single output"); + goto done; + } + } + if (tmpl_len || outputs.count || inputs.count || clobbers.count || + clobber_abi_sets) { + toy_inline_asm(p, tmpl, outputs.items, outputs.count, inputs.items, + inputs.count, clobbers.items, clobbers.count, flags, + clobber_abi_sets); + } + if (cfree_cg_type_kind(p->c, result_ty) == CFREE_CG_TYPE_RECORD && + outputs.count != 0) { + CfreeCgLocal rec_slot = cfree_cg_local(p->cg, result_ty, toy_slot_attrs(0)); + uint32_t i = outputs.count; + while (i > 0) { + CfreeCgField field; + uint32_t field_index; + --i; + field_index = record_field_indexes ? record_field_indexes[i] : i; + if (cfree_cg_type_record_field(p->c, result_ty, field_index, &field, + NULL) != 0) + goto done; + cfree_cg_push_local(p->cg, rec_slot); + cfree_cg_field(p->cg, field_index); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + } + cfree_cg_push_local(p->cg, rec_slot); + } + ok = 1; + +done: + free(record_field_indexes); + free(outputs.items); + free(inputs.items); + free(clobbers.items); + return ok; +} diff --git a/lang/toy/attrs.c b/lang/toy/attrs.c @@ -0,0 +1,397 @@ +#include "internal.h" + +#include <string.h> + +static void toy_attr_set_init(ToyAttrSet* attrs, CfreeSymBind default_bind) { + memset(attrs, 0, sizeof *attrs); + attrs->sym.bind = default_bind; + attrs->sym.visibility = CFREE_CG_VIS_DEFAULT; + attrs->object.tls_model = CFREE_CG_TLS_AUTO; + attrs->call_conv = CFREE_CG_CC_TARGET_C; +} + +int toy_parse_attr_dot_name(ToyParser* p, CfreeSym* out) { + if (!toy_parser_expect(p, TOK_DOT) || + !(p->cur.kind == TOK_IDENT || + (p->cur.kind >= TOK_FN && p->cur.kind <= TOK_INT))) { + toy_error(p, p->cur.loc, "expected attribute name"); + return 0; + } + *out = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + return 1; +} + +static int toy_parse_attr_dot_arg(ToyParser* p, CfreeSym* out) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_attr_dot_name(p, out) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected dot attribute argument"); + return 0; + } + return 1; +} + +int toy_parse_attr_int_arg(ToyParser* p, int64_t* out) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_number_arg(p, out) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected integer attribute argument"); + return 0; + } + return 1; +} + +static int toy_parse_attr_string_arg(ToyParser* p, CfreeSym* out) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_string_sym(p, out, NULL) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected string attribute argument"); + return 0; + } + return 1; +} + +int toy_parse_callconv_const(ToyParser* p, CfreeCgCallConv* out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected call convention"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "target_c")) *out = CFREE_CG_CC_TARGET_C; + else if (toy_sym_is(p, name, "sysv")) *out = CFREE_CG_CC_SYSV; + else if (toy_sym_is(p, name, "win64")) *out = CFREE_CG_CC_WIN64; + else if (toy_sym_is(p, name, "aapcs")) *out = CFREE_CG_CC_AAPCS; + else if (toy_sym_is(p, name, "wasm")) *out = CFREE_CG_CC_WASM; + else if (toy_sym_is(p, name, "interrupt")) *out = CFREE_CG_CC_INTERRUPT; + else { + toy_error(p, p->cur.loc, "unknown call convention"); + return 0; + } + return 1; +} + +static int toy_parse_attr_one(ToyParser* p, ToyAttrSet* attrs) { + CfreeSym name; + CfreeSym arg; + int64_t int_arg; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + + if (toy_sym_is(p, name, "bind")) { + attrs->attr_kinds |= TOY_ATTR_SYMBOL; + if (!toy_parse_attr_dot_arg(p, &arg)) return 0; + if (toy_sym_is(p, arg, "local")) attrs->sym.bind = CFREE_SB_LOCAL; + else if (toy_sym_is(p, arg, "global")) attrs->sym.bind = CFREE_SB_GLOBAL; + else if (toy_sym_is(p, arg, "weak")) attrs->sym.bind = CFREE_SB_WEAK; + else { + toy_error(p, p->cur.loc, "unknown bind attribute"); + return 0; + } + return 1; + } + if (toy_sym_is(p, name, "visibility")) { + attrs->attr_kinds |= TOY_ATTR_SYMBOL; + if (!toy_parse_attr_dot_arg(p, &arg)) return 0; + if (toy_sym_is(p, arg, "default")) + attrs->sym.visibility = CFREE_CG_VIS_DEFAULT; + else if (toy_sym_is(p, arg, "hidden")) + attrs->sym.visibility = CFREE_CG_VIS_HIDDEN; + else if (toy_sym_is(p, arg, "protected")) + attrs->sym.visibility = CFREE_CG_VIS_PROTECTED; + else { + toy_error(p, p->cur.loc, "unknown visibility attribute"); + return 0; + } + return 1; + } + if (toy_sym_is(p, name, "used")) { + attrs->attr_kinds |= TOY_ATTR_SYMBOL; + attrs->sym.flags |= CFREE_CG_SYM_USED; + return 1; + } + if (toy_sym_is(p, name, "dllimport")) { + attrs->attr_kinds |= TOY_ATTR_SYMBOL; + attrs->sym.flags |= CFREE_CG_SYM_DLLIMPORT; + return 1; + } + if (toy_sym_is(p, name, "dllexport")) { + attrs->attr_kinds |= TOY_ATTR_SYMBOL; + attrs->sym.flags |= CFREE_CG_SYM_DLLEXPORT; + return 1; + } + if (toy_sym_is(p, name, "section")) { + CfreeSym section; + attrs->attr_kinds |= TOY_ATTR_SECTION; + if (!toy_parse_attr_string_arg(p, &section)) return 0; + attrs->func.section = section; + attrs->object.section = section; + attrs->data.section = section; + return 1; + } + if (toy_sym_is(p, name, "noreturn")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_NORETURN; + return 1; + } + if (toy_sym_is(p, name, "ifunc")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_IFUNC; + return 1; + } + if (toy_sym_is(p, name, "cold")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_COLD; + return 1; + } + if (toy_sym_is(p, name, "hot")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_HOT; + return 1; + } + if (toy_sym_is(p, name, "naked")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_NAKED; + return 1; + } + if (toy_sym_is(p, name, "interrupt")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_INTERRUPT; + return 1; + } + if (toy_sym_is(p, name, "no_red_zone")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + attrs->func.flags |= CFREE_CG_FUNC_NO_RED_ZONE; + return 1; + } + if (toy_sym_is(p, name, "stack_align")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + attrs->func.stack_align = (uint32_t)int_arg; + return 1; + } + if (toy_sym_is(p, name, "target_features")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + if (!toy_parse_attr_string_arg(p, &attrs->func.target_features)) return 0; + return 1; + } + if (toy_sym_is(p, name, "callconv")) { + attrs->attr_kinds |= TOY_ATTR_FUNC; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_callconv_const(p, &attrs->call_conv) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected callconv attribute argument"); + return 0; + } + attrs->has_call_conv = 1; + return 1; + } + if (toy_sym_is(p, name, "align")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT_DATA; + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + attrs->object.align = (uint32_t)int_arg; + attrs->data.align = (uint32_t)int_arg; + return 1; + } + if (toy_sym_is(p, name, "readonly")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT_DATA; + attrs->object.flags |= CFREE_CG_OBJ_READONLY; + attrs->data.flags |= CFREE_CG_DATADEF_READONLY; + return 1; + } + if (toy_sym_is(p, name, "threadlocal")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT; + attrs->object.flags |= CFREE_CG_OBJ_TLS; + return 1; + } + if (toy_sym_is(p, name, "tls_model")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT; + if (!toy_parse_attr_dot_arg(p, &arg)) return 0; + if (toy_sym_is(p, arg, "auto")) attrs->object.tls_model = CFREE_CG_TLS_AUTO; + else if (toy_sym_is(p, arg, "local_exec")) + attrs->object.tls_model = CFREE_CG_TLS_LOCAL_EXEC; + else if (toy_sym_is(p, arg, "initial_exec")) + attrs->object.tls_model = CFREE_CG_TLS_INITIAL_EXEC; + else if (toy_sym_is(p, arg, "local_dynamic")) + attrs->object.tls_model = CFREE_CG_TLS_LOCAL_DYNAMIC; + else if (toy_sym_is(p, arg, "general_dynamic")) + attrs->object.tls_model = CFREE_CG_TLS_GENERAL_DYNAMIC; + else { + toy_error(p, p->cur.loc, "unknown tls_model attribute"); + return 0; + } + return 1; + } + if (toy_sym_is(p, name, "static")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT; + attrs->has_static = 1; + return 1; + } + if (toy_sym_is(p, name, "common")) { + attrs->attr_kinds |= TOY_ATTR_OBJECT; + attrs->is_common = 1; + return 1; + } + if (toy_sym_is(p, name, "retain")) { + attrs->attr_kinds |= TOY_ATTR_DATA; + attrs->data.flags |= CFREE_CG_DATADEF_RETAIN; + return 1; + } + if (toy_sym_is(p, name, "merge")) { + attrs->attr_kinds |= TOY_ATTR_DATA; + attrs->data.flags |= CFREE_CG_DATADEF_MERGE; + return 1; + } + if (toy_sym_is(p, name, "strings")) { + attrs->attr_kinds |= TOY_ATTR_DATA; + attrs->data.flags |= CFREE_CG_DATADEF_STRINGS; + return 1; + } + if (toy_sym_is(p, name, "entsize")) { + attrs->attr_kinds |= TOY_ATTR_DATA; + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + attrs->data.entsize = (uint32_t)int_arg; + return 1; + } + + toy_error(p, p->cur.loc, "unknown attribute"); + return 0; +} + +int toy_parse_attr_list(ToyParser* p, ToyAttrSet* attrs, + CfreeSymBind default_bind) { + toy_attr_set_init(attrs, default_bind); + if (!toy_parser_match(p, TOK_AT)) return 1; + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected '[' after '@'"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + if (!toy_parse_attr_one(p, attrs)) return 0; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after attribute list"); + return 0; + } + return 1; +} + +int toy_validate_attr_placement(ToyParser* p, const ToyAttrSet* attrs, + uint32_t allowed, const char* message) { + if (attrs->attr_kinds & ~allowed) { + toy_error(p, p->cur.loc, message); + return 0; + } + return 1; +} + +static int toy_parse_abi_attr_one(ToyParser* p, CfreeCgAbiAttrs* attrs) { + CfreeSym name; + int64_t int_arg; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "signext")) attrs->flags |= CFREE_CG_ABI_SIGNEXT; + else if (toy_sym_is(p, name, "zeroext")) + attrs->flags |= CFREE_CG_ABI_ZEROEXT; + else if (toy_sym_is(p, name, "sret")) attrs->flags |= CFREE_CG_ABI_SRET; + else if (toy_sym_is(p, name, "byval")) attrs->flags |= CFREE_CG_ABI_BYVAL; + else if (toy_sym_is(p, name, "byref")) attrs->flags |= CFREE_CG_ABI_BYREF; + else if (toy_sym_is(p, name, "inreg")) attrs->flags |= CFREE_CG_ABI_INREG; + else if (toy_sym_is(p, name, "noalias")) + attrs->flags |= CFREE_CG_ABI_NOALIAS; + else if (toy_sym_is(p, name, "readonly")) + attrs->flags |= CFREE_CG_ABI_READONLY; + else if (toy_sym_is(p, name, "writeonly")) + attrs->flags |= CFREE_CG_ABI_WRITEONLY; + else if (toy_sym_is(p, name, "nonnull")) + attrs->flags |= CFREE_CG_ABI_NONNULL; + else if (toy_sym_is(p, name, "nest")) attrs->flags |= CFREE_CG_ABI_NEST; + else if (toy_sym_is(p, name, "align")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + attrs->align = (uint32_t)int_arg; + } else if (toy_sym_is(p, name, "dereferenceable")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + attrs->dereferenceable_size = (uint64_t)int_arg; + } else { + toy_error(p, p->cur.loc, "unknown ABI attribute"); + return 0; + } + return 1; +} + +int toy_parse_abi_attr_list(ToyParser* p, CfreeCgAbiAttrs* attrs) { + memset(attrs, 0, sizeof *attrs); + if (!toy_parser_match(p, TOK_AT)) return 1; + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected '[' after '@'"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + if (!toy_parse_abi_attr_one(p, attrs)) return 0; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after ABI attribute list"); + return 0; + } + return 1; +} + +int toy_parse_field_attr_list(ToyParser* p, CfreeCgField* field) { + if (!toy_parser_match(p, TOK_AT)) return 1; + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected '[' after '@'"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + CfreeSym name; + int64_t int_arg; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "align")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + field->align_override = (uint32_t)int_arg; + } else if (toy_sym_is(p, name, "packed")) { + field->align_override = 1; + } else { + toy_error(p, p->cur.loc, "unknown field attribute"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after field attribute list"); + return 0; + } + return 1; +} + +int toy_parse_record_attr_list(ToyParser* p, int* packed_out, + uint32_t* align_out) { + *packed_out = 0; + *align_out = 0; + if (!toy_parser_match(p, TOK_AT)) return 1; + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected '[' after '@'"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + CfreeSym name; + int64_t int_arg; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "packed")) { + *packed_out = 1; + } else if (toy_sym_is(p, name, "align")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + *align_out = (uint32_t)int_arg; + } else { + toy_error(p, p->cur.loc, "unknown record attribute"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after record attribute list"); + return 0; + } + return 1; +} diff --git a/lang/toy/builtins.c b/lang/toy/builtins.c @@ -0,0 +1,1323 @@ +#include "internal.h" + +#include <string.h> + +static CfreeCgMemAccess toy_mem_access_align(ToyParser* p, CfreeCgTypeId type, + uint32_t align) { + CfreeCgMemAccess access = toy_mem_access(p, type); + access.align = align; + return access; +} + +static int toy_parse_mem_flags_tail(ToyParser* p, CfreeCgMemAccess* access) { + while (toy_parser_match(p, TOK_COMMA)) { + CfreeSym flag; + if (!toy_parse_attr_dot_name(p, &flag)) return 0; + if (toy_sym_is(p, flag, "volatile")) + access->flags |= CFREE_CG_MEM_VOLATILE; + else if (toy_sym_is(p, flag, "nontemporal")) + access->flags |= CFREE_CG_MEM_NONTEMPORAL; + else if (toy_sym_is(p, flag, "invariant")) + access->flags |= CFREE_CG_MEM_INVARIANT; + else { + toy_error(p, p->cur.loc, "unknown memory access flag"); + return 0; + } + } + return 1; +} + +static void toy_store_top_to_local(ToyParser* p, CfreeCgLocal local, + CfreeCgTypeId ty) { + cfree_cg_push_local(p->cg, local); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, ty)); +} + +static void toy_store_const_to_local(ToyParser* p, CfreeCgLocal local, + CfreeCgTypeId ty, uint64_t value) { + cfree_cg_push_local(p->cg, local); + cfree_cg_push_int(p->cg, value, ty); + cfree_cg_store(p->cg, toy_mem_access(p, ty)); +} + +static void toy_push_loaded_local(ToyParser* p, CfreeCgLocal local, + CfreeCgTypeId ty) { + cfree_cg_push_local(p->cg, local); + cfree_cg_load(p->cg, toy_mem_access(p, ty)); +} + +static void toy_emit_dynamic_memory_loop(ToyParser* p, CfreeCgLocal dst_local, + CfreeCgLocal src_local, + CfreeCgLocal size_local, + int is_memset, uint8_t set_value) { + CfreeCgTypeId u8_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + CfreeCgTypeId u8_ptr_ty = cfree_cg_type_ptr(p->c, u8_ty, 0); + CfreeCgLocal index_local = + cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + CfreeCgLabel loop_label = cfree_cg_label_new(p->cg); + CfreeCgLabel end_label = cfree_cg_label_new(p->cg); + + toy_store_const_to_local(p, index_local, p->int_type, 0); + cfree_cg_label_place(p->cg, loop_label); + + toy_push_loaded_local(p, index_local, p->int_type); + toy_push_loaded_local(p, size_local, p->int_type); + cfree_cg_int_cmp(p->cg, CFREE_CG_INT_LT_U); + cfree_cg_branch_false(p->cg, end_label); + + toy_push_loaded_local(p, dst_local, u8_ptr_ty); + toy_push_loaded_local(p, index_local, p->int_type); + cfree_cg_index(p->cg, 0); + if (is_memset) { + cfree_cg_push_int(p->cg, set_value, u8_ty); + } else { + toy_push_loaded_local(p, src_local, u8_ptr_ty); + toy_push_loaded_local(p, index_local, p->int_type); + cfree_cg_index(p->cg, 0); + cfree_cg_load(p->cg, toy_mem_access(p, u8_ty)); + } + cfree_cg_store(p->cg, toy_mem_access(p, u8_ty)); + + cfree_cg_push_local(p->cg, index_local); + toy_push_loaded_local(p, index_local, p->int_type); + cfree_cg_push_int(p->cg, 1, p->int_type); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_ADD, 0); + cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); + + cfree_cg_jump(p->cg, loop_label); + cfree_cg_label_place(p->cg, end_label); +} + +static int toy_parse_memory_align_operand(ToyParser* p, int64_t* align, + int* dynamic) { + *align = 0; + *dynamic = 0; + if (!toy_parser_match(p, TOK_COMMA)) return 1; + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) + return toy_parse_number_arg(p, align); + { + CfreeCgTypeId ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + if (ty != p->int_type) { + toy_error(p, p->cur.loc, "memory alignment must be i64"); + return 0; + } + cfree_cg_drop(p->cg); + *dynamic = 1; + } + return 1; +} + +static int toy_parse_access_group(ToyParser* p, CfreeCgMemAccess* access) { + CfreeSym access_name = cfree_sym_intern(p->c, "access"); + if (p->cur.kind != TOK_IDENT || toy_tok_sym(p, p->cur) != access_name) + return 0; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected '(' after access"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeSym name; + int64_t int_arg; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "align")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + access->align = (uint32_t)int_arg; + } else if (toy_sym_is(p, name, "addrspace")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + access->address_space = (uint32_t)int_arg; + } else if (toy_sym_is(p, name, "volatile")) { + access->flags |= CFREE_CG_MEM_VOLATILE; + } else if (toy_sym_is(p, name, "nontemporal")) { + access->flags |= CFREE_CG_MEM_NONTEMPORAL; + } else if (toy_sym_is(p, name, "invariant")) { + access->flags |= CFREE_CG_MEM_INVARIANT; + } else if (toy_sym_is(p, name, "alias_scope")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + access->alias_scope = (uint32_t)int_arg; + } else if (toy_sym_is(p, name, "noalias_scope")) { + if (!toy_parse_attr_int_arg(p, &int_arg) || int_arg < 0) return 0; + access->noalias_scope = (uint32_t)int_arg; + } else { + toy_error(p, p->cur.loc, "unknown access entry"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after access group"); + return 0; + } + return 1; +} + +static int toy_parse_optional_access_arg(ToyParser* p, + CfreeCgMemAccess* access) { + if (!toy_parser_match(p, TOK_COMMA)) return 1; + if (!toy_parse_access_group(p, access)) { + toy_error(p, p->cur.loc, "expected access group"); + return 0; + } + return 1; +} + +static int toy_parse_mem_order(ToyParser* p, CfreeCgMemOrder* out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected memory order"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "relaxed")) *out = CFREE_CG_MO_RELAXED; + else if (toy_sym_is(p, name, "consume")) *out = CFREE_CG_MO_CONSUME; + else if (toy_sym_is(p, name, "acquire")) *out = CFREE_CG_MO_ACQUIRE; + else if (toy_sym_is(p, name, "release")) *out = CFREE_CG_MO_RELEASE; + else if (toy_sym_is(p, name, "acq_rel")) *out = CFREE_CG_MO_ACQ_REL; + else if (toy_sym_is(p, name, "seq_cst")) *out = CFREE_CG_MO_SEQ_CST; + else { + toy_error(p, p->cur.loc, "unknown memory order"); + return 0; + } + return 1; +} + +static int toy_parse_atomic_op(ToyParser* p, CfreeCgAtomicOp* out) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "xchg")) *out = CFREE_CG_ATOMIC_XCHG; + else if (toy_sym_is(p, name, "add")) *out = CFREE_CG_ATOMIC_ADD; + else if (toy_sym_is(p, name, "sub")) *out = CFREE_CG_ATOMIC_SUB; + else if (toy_sym_is(p, name, "and")) *out = CFREE_CG_ATOMIC_AND; + else if (toy_sym_is(p, name, "or")) *out = CFREE_CG_ATOMIC_OR; + else if (toy_sym_is(p, name, "xor")) *out = CFREE_CG_ATOMIC_XOR; + else if (toy_sym_is(p, name, "nand")) *out = CFREE_CG_ATOMIC_NAND; + else { + toy_error(p, p->cur.loc, "unknown atomic op"); + return 0; + } + return 1; +} + +static int toy_parse_cmpxchg_strength(ToyParser* p, int* weak_out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected compare-exchange strength"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "strong")) *weak_out = 0; + else if (toy_sym_is(p, name, "weak")) *weak_out = 1; + else { + toy_error(p, p->cur.loc, "unknown compare-exchange strength"); + return 0; + } + return 1; +} + +static int toy_parse_barrier_scope(ToyParser* p, CfreeCgBarrierScope* out) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "full")) *out = CFREE_CG_BARRIER_FULL; + else if (toy_sym_is(p, name, "inner")) *out = CFREE_CG_BARRIER_INNER; + else if (toy_sym_is(p, name, "inner_store")) + *out = CFREE_CG_BARRIER_INNER_STORE; + else if (toy_sym_is(p, name, "outer")) *out = CFREE_CG_BARRIER_OUTER; + else if (toy_sym_is(p, name, "outer_store")) + *out = CFREE_CG_BARRIER_OUTER_STORE; + else if (toy_sym_is(p, name, "non_share")) + *out = CFREE_CG_BARRIER_NON_SHARE; + else { + toy_error(p, p->cur.loc, "unknown barrier scope"); + return 0; + } + return 1; +} + +static CfreeCgTypeId toy_unsupported_intrinsic(ToyParser* p) { + toy_error(p, p->cur.loc, "unsupported target intrinsic"); + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, + int* recognized) { + *recognized = 1; + + if (toy_sym_is(p, name, "popcount") || toy_sym_is(p, name, "ctz") || + toy_sym_is(p, name, "clz") || toy_sym_is(p, name, "bswap")) { + CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_POPCOUNT; + CfreeCgTypeId ty; + if (toy_sym_is(p, name, "ctz")) intrin = CFREE_CG_INTRIN_CTZ; + else if (toy_sym_is(p, name, "clz")) intrin = CFREE_CG_INTRIN_CLZ; + else if (toy_sym_is(p, name, "bswap")) intrin = CFREE_CG_INTRIN_BSWAP; + toy_parser_advance(p); /* ( */ + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')'"); + return CFREE_CG_TYPE_NONE; + } + if (!toy_type_is_intlike(p, ty)) { + toy_error(p, p->cur.loc, "integer intrinsic expects integer operand"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_intrinsic(p->cg, intrin, 1, ty); + return ty; + } + + if (toy_sym_is(p, name, "expect")) { + CfreeCgTypeId a, b; + toy_parser_advance(p); + a = toy_parse_expr(p); + if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + b = toy_parse_expr(p); + if (b == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (a != b) { + toy_error(p, p->cur.loc, "expect operands must have matching type"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_EXPECT, 2, a); + return a; + } + + if (toy_sym_is(p, name, "trap")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_TRAP, 0, + toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "cpu_nop") || toy_sym_is(p, name, "cpu_yield") || + toy_sym_is(p, name, "irq_disable") || toy_sym_is(p, name, "irq_enable") || + toy_sym_is(p, name, "isb")) { + CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_CPU_NOP; + if (toy_sym_is(p, name, "cpu_yield")) intrin = CFREE_CG_INTRIN_CPU_YIELD; + else if (toy_sym_is(p, name, "irq_disable")) + intrin = CFREE_CG_INTRIN_IRQ_DISABLE; + else if (toy_sym_is(p, name, "irq_enable")) + intrin = CFREE_CG_INTRIN_IRQ_ENABLE; + else if (toy_sym_is(p, name, "isb")) + intrin = CFREE_CG_INTRIN_ISB; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_intrinsic(p->cg, intrin, 0, + toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + { + int low_level_recognized = 0; + CfreeCgTypeId low_level_ty = + toy_parse_low_level_builtin_call(p, name, &low_level_recognized); + if (low_level_recognized) return low_level_ty; + } + + if (toy_sym_is(p, name, "compile_error")) { + CfreeSym msg; + size_t msg_len; + const char* text; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_string_sym(p, &msg, &msg_len) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + text = cfree_sym_str(p->c, msg, NULL); + toy_error(p, p->cur.loc, "compile_error: %s", text ? text : ""); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "unreachable")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_unreachable(p->cg); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "bitget")) { + CfreeCgTypeId ty; + int64_t lo, width; + toy_parser_advance(p); + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &lo) || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &width) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_validate_bit_range(p, ty, lo, width, 1)) { + toy_error(p, p->cur.loc, "invalid bitget arguments"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_bitget(p->cg, ty, (uint32_t)lo, (uint32_t)width); + return ty; + } + + if (toy_sym_is(p, name, "bitset")) { + CfreeCgTypeId dst_ty, src_ty; + int64_t lo, width; + CfreeCgLocal dst_slot, src_slot; + uint64_t src_mask, field_mask, clear_mask; + toy_parser_advance(p); + dst_ty = toy_parse_expr(p); + if (dst_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + src_ty = toy_parse_expr(p); + if (src_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &lo) || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &width) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (dst_ty != src_ty || !toy_validate_bit_range(p, dst_ty, lo, width, 0)) { + toy_error(p, p->cur.loc, "invalid bitset arguments"); + return CFREE_CG_TYPE_NONE; + } + src_mask = (1ULL << (uint32_t)width) - 1u; + field_mask = src_mask << (uint32_t)lo; + clear_mask = ~field_mask; + src_slot = cfree_cg_local(p->cg, src_ty, toy_slot_attrs(0)); + dst_slot = cfree_cg_local(p->cg, dst_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, src_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, src_ty)); + cfree_cg_push_local(p->cg, dst_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, dst_ty)); + + cfree_cg_push_local(p->cg, dst_slot); + cfree_cg_load(p->cg, toy_mem_access(p, dst_ty)); + cfree_cg_push_int(p->cg, clear_mask, dst_ty); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); + cfree_cg_push_local(p->cg, src_slot); + cfree_cg_load(p->cg, toy_mem_access(p, src_ty)); + cfree_cg_push_int(p->cg, src_mask, src_ty); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); + if (lo > 0) { + cfree_cg_push_int(p->cg, (uint64_t)lo, src_ty); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_SHL, 0); + } + cfree_cg_int_binop(p->cg, CFREE_CG_INT_OR, 0); + return dst_ty; + } + + if (toy_sym_is(p, name, "fma")) { + CfreeCgTypeId a, b, c; + toy_parser_advance(p); + a = toy_parse_expr(p); + if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + b = toy_parse_expr(p); + if (b == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + c = toy_parse_expr(p); + if (c == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (a != b || a != c || !toy_type_is_float(p, a)) { + toy_error(p, p->cur.loc, "fma expects matching float operands"); + return CFREE_CG_TYPE_NONE; + } + { + CfreeCgLocal c_slot = cfree_cg_local(p->cg, a, toy_slot_attrs(0)); + CfreeCgLocal b_slot = cfree_cg_local(p->cg, a, toy_slot_attrs(0)); + CfreeCgLocal a_slot = cfree_cg_local(p->cg, a, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, c_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, a)); + cfree_cg_push_local(p->cg, b_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, a)); + cfree_cg_push_local(p->cg, a_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, a)); + cfree_cg_push_local(p->cg, a_slot); + cfree_cg_load(p->cg, toy_mem_access(p, a)); + cfree_cg_push_local(p->cg, b_slot); + cfree_cg_load(p->cg, toy_mem_access(p, a)); + cfree_cg_fp_binop(p->cg, CFREE_CG_FP_MUL, 0); + cfree_cg_push_local(p->cg, c_slot); + cfree_cg_load(p->cg, toy_mem_access(p, a)); + cfree_cg_fp_binop(p->cg, CFREE_CG_FP_ADD, 0); + } + return a; + } + + if (toy_sym_is(p, name, "prefetch")) { + CfreeCgTypeId ptr_ty; + toy_parser_advance(p); + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty)) { + toy_error(p, p->cur.loc, "prefetch expects pointer"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_PREFETCH, 1, + toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + { + int memory_recognized = 0; + CfreeCgTypeId memory_ty = + toy_parse_memory_builtin_call(p, name, &memory_recognized); + if (memory_recognized) return memory_ty; + } + + if (toy_sym_is(p, name, "labeladdr")) { + CfreeSym label_name; + ToyLabel* label; + CfreeCgTypeId ptr_ty = + cfree_cg_type_ptr(p->c, toy_builtin_type(p, CFREE_CG_BUILTIN_VOID), 0); + if (!toy_parser_expect(p, TOK_LPAREN) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected label name"); + return CFREE_CG_TYPE_NONE; + } + label_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + label = toy_find_label(p, label_name); + if (!label) { + toy_error(p, p->cur.loc, "unknown label"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_push_label_addr(p->cg, label->label, ptr_ty); + return ptr_ty; + } + + if (toy_sym_is(p, name, "target_arch")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, (uint64_t)toy_target_code(p), p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "supports_callconv")) { + CfreeCgCallConv cc; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_callconv_const(p, &cc) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, + cfree_cg_target_supports_call_conv(p->c, cc) ? 1u : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + if (toy_sym_is(p, name, "supports_symbol_feature")) { + CfreeCgSymbolFeature feature; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_symbol_feature_const(p, &feature) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int( + p->cg, + cfree_cg_target_supports_symbol_feature(p->c, feature) ? 1u : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + if (toy_sym_is(p, name, "has_backend_feature")) { + uint64_t feature; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_backend_feature_const(p, &feature) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int( + p->cg, + (cfree_cg_target_backend_features(p->c) & feature) ? 1u : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + if (toy_sym_is(p, name, "va_start")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_vararg_start(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_end")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_vararg_end(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_copy")) { + if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_expect_comma(p) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_vararg_copy(p->cg); + cfree_cg_push_int(p->cg, 0, p->int_type); + return p->int_type; + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized) { + CfreeCgTypeId ty; + *recognized = 1; + + if (toy_sym_is(p, name, "alloca")) { + CfreeCgTypeId count_ty; + CfreeCgTypeId ptr_ty; + ToyTypeId elem_toy_type; + int64_t align; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + elem_toy_type = p->last_type; + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + count_ty = toy_parse_expr(p); + if (count_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &align) || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (count_ty != p->int_type || align <= 0) { + toy_error(p, p->cur.loc, "invalid alloca arguments"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_push_int(p->cg, cfree_cg_type_size(p->c, ty), p->int_type); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_MUL, 0); + ptr_ty = cfree_cg_type_ptr(p->c, ty, 0); + cfree_cg_alloca(p->cg, (uint32_t)align, ptr_ty); + p->last_type = toy_type_register_ptr(p, ptr_ty, elem_toy_type, 0); + return ptr_ty; + } + + if (toy_sym_is(p, name, "asm")) { + CfreeSym tmpl; + size_t tmpl_len; + ToyTypeId result_toy_type; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + result_toy_type = p->last_type; + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len)) { + return CFREE_CG_TYPE_NONE; + } + if (!toy_parse_typed_asm_tail(p, ty, tmpl, tmpl_len)) + return CFREE_CG_TYPE_NONE; + p->last_type = result_toy_type; + return ty; + } + + { + int low_level_recognized = 0; + CfreeCgTypeId low_level_ty = + toy_parse_low_level_generic_builtin(p, name, &low_level_recognized); + if (low_level_recognized) return low_level_ty; + } + + if (toy_sym_is(p, name, "sext") || toy_sym_is(p, name, "zext") || + toy_sym_is(p, name, "trunc") || toy_sym_is(p, name, "ptr_to_int") || + toy_sym_is(p, name, "int_to_ptr") || toy_sym_is(p, name, "bitcast") || + toy_sym_is(p, name, "fpext") || toy_sym_is(p, name, "fptrunc")) { + CfreeCgTypeId src_ty; + ToyTypeId result_toy_type; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + result_toy_type = p->last_type; + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + src_ty = toy_parse_expr(p); + if (src_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + (void)src_ty; + if (toy_sym_is(p, name, "sext")) cfree_cg_sext(p->cg, ty); + else if (toy_sym_is(p, name, "zext")) cfree_cg_zext(p->cg, ty); + else if (toy_sym_is(p, name, "trunc")) cfree_cg_trunc(p->cg, ty); + else if (toy_sym_is(p, name, "ptr_to_int")) cfree_cg_ptr_to_int(p->cg, ty); + else if (toy_sym_is(p, name, "int_to_ptr")) cfree_cg_int_to_ptr(p->cg, ty); + else if (toy_sym_is(p, name, "bitcast")) cfree_cg_bitcast(p->cg, ty); + else if (toy_sym_is(p, name, "fpext")) cfree_cg_fpext(p->cg, ty); + else if (toy_sym_is(p, name, "fptrunc")) cfree_cg_fptrunc(p->cg, ty); + p->last_type = result_toy_type; + return ty; + } + + if (toy_sym_is(p, name, "sint_to_float") || + toy_sym_is(p, name, "uint_to_float") || + toy_sym_is(p, name, "float_to_sint") || + toy_sym_is(p, name, "float_to_uint")) { + CfreeCgTypeId src_ty; + CfreeCgRounding rounding; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + src_ty = toy_parse_expr(p); + if (src_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_rounding_const(p, &rounding) || + !toy_parser_expect(p, TOK_RPAREN)) { + return CFREE_CG_TYPE_NONE; + } + (void)src_ty; + if (toy_sym_is(p, name, "sint_to_float")) + cfree_cg_sint_to_float(p->cg, ty, rounding); + else if (toy_sym_is(p, name, "uint_to_float")) + cfree_cg_uint_to_float(p->cg, ty, rounding); + else if (toy_sym_is(p, name, "float_to_sint")) + cfree_cg_float_to_sint(p->cg, ty, rounding); + else + cfree_cg_float_to_uint(p->cg, ty, rounding); + return ty; + } + + if (toy_sym_is(p, name, "assume_aligned")) { + CfreeCgTypeId ptr_ty; + int64_t align; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &align) || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (ptr_ty != ty || !toy_type_is_ptr(p, ty) || align <= 0) { + toy_error(p, p->cur.loc, "invalid assume_aligned arguments"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_push_int(p->cg, (uint64_t)align, p->int_type); + cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_ASSUME_ALIGNED, 2, ty); + return ty; + } + + if (toy_sym_is(p, name, "sizeof") || toy_sym_is(p, name, "alignof")) { + int is_align = toy_sym_is(p, name, "alignof"); + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected type query form"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_push_int(p->cg, + is_align ? cfree_cg_type_align(p->c, ty) + : cfree_cg_type_size(p->c, ty), + p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "offsetof")) { + uint32_t i, nfields; + uint64_t off = 0; + int found = 0; + CfreeSym field_name = 0; + int64_t tuple_index = -1; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || + (p->cur.kind != TOK_IDENT && + !(p->cur.kind == TOK_NUMBER && !p->cur.is_float))) { + toy_error(p, p->cur.loc, "expected offsetof<T>(field)"); + return CFREE_CG_TYPE_NONE; + } + if (p->cur.kind == TOK_NUMBER) + tuple_index = p->cur.int_value; + else + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD) { + toy_error(p, p->cur.loc, "offsetof expects a record type"); + return CFREE_CG_TYPE_NONE; + } + nfields = cfree_cg_type_record_nfields(p->c, ty); + if (tuple_index >= 0) { + if (tuple_index >= (int64_t)nfields || + cfree_cg_type_record_field(p->c, ty, (uint32_t)tuple_index, NULL, + &off) != 0) { + toy_error(p, p->cur.loc, "invalid tuple field"); + return CFREE_CG_TYPE_NONE; + } + found = 1; + } else { + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + uint64_t field_off = 0; + if (cfree_cg_type_record_field(p->c, ty, i, &field, &field_off) == 0 && + field.name == field_name) { + off = field_off; + found = 1; + break; + } + } + if (!found) { + toy_error(p, p->cur.loc, "unknown record field"); + return CFREE_CG_TYPE_NONE; + } + } + cfree_cg_push_int(p->cg, off, p->int_type); + return p->int_type; + } + + if (toy_sym_is(p, name, "va_arg")) { + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_vararg_next(p->cg, ty); + return ty; + } + + { + int atomic_recognized = 0; + CfreeCgTypeId atomic_ty = + toy_parse_atomic_generic_builtin(p, name, &atomic_recognized); + if (atomic_recognized) return atomic_ty; + } + + if (toy_sym_is(p, name, "add_overflow") || + toy_sym_is(p, name, "sub_overflow") || + toy_sym_is(p, name, "mul_overflow")) { + CfreeCgTypeId lhs_ty, rhs_ty, rec_ty; + CfreeCgField fields[2]; + CfreeCgLocal rec_slot; + CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_SADD_OVERFLOW; + if (toy_sym_is(p, name, "sub_overflow")) + intrin = CFREE_CG_INTRIN_SSUB_OVERFLOW; + else if (toy_sym_is(p, name, "mul_overflow")) + intrin = CFREE_CG_INTRIN_SMUL_OVERFLOW; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + lhs_ty = toy_parse_expr(p); + if (lhs_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + rhs_ty = toy_parse_expr(p); + if (rhs_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (lhs_ty != ty || rhs_ty != ty || !toy_type_is_intlike(p, ty)) { + toy_error(p, p->cur.loc, "overflow operand type mismatch"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_intrinsic(p->cg, intrin, 2, ty); + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(p->c, "value"); + fields[0].type = ty; + fields[1].name = cfree_sym_intern(p->c, "overflow"); + fields[1].type = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + rec_ty = cfree_cg_type_record(p->c, 0, fields, 2); + rec_slot = cfree_cg_local(p->cg, rec_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, rec_slot); + cfree_cg_field(p->cg, 1); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, fields[1].type)); + cfree_cg_push_local(p->cg, rec_slot); + cfree_cg_field(p->cg, 0); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, fields[0].type)); + cfree_cg_push_local(p->cg, rec_slot); + return rec_ty; + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_memory_builtin_call(ToyParser* p, CfreeSym name, + int* recognized) { + *recognized = 1; + + if (toy_sym_is(p, name, "memset")) { + CfreeCgTypeId dst; + int64_t val, size = 0, align = 0; + int dynamic_size = 0, dynamic_align = 0; + CfreeCgLocal dst_local = CFREE_CG_LOCAL_NONE; + CfreeCgLocal size_local = CFREE_CG_LOCAL_NONE; + CfreeCgMemAccess access; + CfreeCgTypeId u8_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + CfreeCgTypeId u8_ptr_ty = cfree_cg_type_ptr(p->c, u8_ty, 0); + toy_parser_advance(p); + dst = toy_parse_expr(p); + if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_number_arg(p, &val) || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (!toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; + } else { + CfreeCgTypeId size_ty = toy_parse_expr(p); + if (size_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (size_ty != p->int_type) { + toy_error(p, p->cur.loc, "memory size must be i64"); + return CFREE_CG_TYPE_NONE; + } + dynamic_size = 1; + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_top_to_local(p, size_local, p->int_type); + } + if (!toy_parse_memory_align_operand(p, &align, &dynamic_align)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access_align(p, p->int_type, (uint32_t)align); + if (!toy_parse_mem_flags_tail(p, &access)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (dynamic_size || dynamic_align) { + if (val < 0 || val > 255) { + toy_error(p, p->cur.loc, "memset value out of range"); + return CFREE_CG_TYPE_NONE; + } + if (!dynamic_size) { + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_const_to_local(p, size_local, p->int_type, (uint64_t)size); + } + dst_local = cfree_cg_local(p->cg, u8_ptr_ty, toy_slot_attrs(0)); + cfree_cg_bitcast(p->cg, u8_ptr_ty); + toy_store_top_to_local(p, dst_local, u8_ptr_ty); + toy_emit_dynamic_memory_loop(p, dst_local, CFREE_CG_LOCAL_NONE, + size_local, 1, (uint8_t)val); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + cfree_cg_memset(p->cg, (uint8_t)val, (uint64_t)size, access); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "memcpy")) { + CfreeCgTypeId dst, src; + int64_t size = 0, align = 0; + int dynamic_size = 0, dynamic_align = 0; + CfreeCgLocal dst_local = CFREE_CG_LOCAL_NONE; + CfreeCgLocal src_local = CFREE_CG_LOCAL_NONE; + CfreeCgLocal size_local = CFREE_CG_LOCAL_NONE; + CfreeCgMemAccess access; + CfreeCgTypeId u8_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + CfreeCgTypeId u8_ptr_ty = cfree_cg_type_ptr(p->c, u8_ty, 0); + toy_parser_advance(p); + dst = toy_parse_expr(p); + if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + src = toy_parse_expr(p); + if (src == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (!toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; + } else { + CfreeCgTypeId size_ty = toy_parse_expr(p); + if (size_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (size_ty != p->int_type) { + toy_error(p, p->cur.loc, "memory size must be i64"); + return CFREE_CG_TYPE_NONE; + } + dynamic_size = 1; + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_top_to_local(p, size_local, p->int_type); + } + if (!toy_parse_memory_align_operand(p, &align, &dynamic_align)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access_align(p, p->int_type, (uint32_t)align); + if (!toy_parse_mem_flags_tail(p, &access)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (dynamic_size || dynamic_align) { + if (!dynamic_size) { + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_const_to_local(p, size_local, p->int_type, (uint64_t)size); + } + src_local = cfree_cg_local(p->cg, u8_ptr_ty, toy_slot_attrs(0)); + cfree_cg_bitcast(p->cg, u8_ptr_ty); + toy_store_top_to_local(p, src_local, u8_ptr_ty); + dst_local = cfree_cg_local(p->cg, u8_ptr_ty, toy_slot_attrs(0)); + cfree_cg_bitcast(p->cg, u8_ptr_ty); + toy_store_top_to_local(p, dst_local, u8_ptr_ty); + toy_emit_dynamic_memory_loop(p, dst_local, src_local, size_local, 0, 0); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + cfree_cg_memcpy(p->cg, (uint64_t)size, access, access); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "memmove")) { + CfreeCgTypeId dst, src; + int64_t size = 0, align = 0; + int dynamic_size = 0, dynamic_align = 0; + CfreeCgLocal dst_local = CFREE_CG_LOCAL_NONE; + CfreeCgLocal src_local = CFREE_CG_LOCAL_NONE; + CfreeCgLocal size_local = CFREE_CG_LOCAL_NONE; + CfreeCgMemAccess access; + CfreeCgTypeId u8_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + CfreeCgTypeId u8_ptr_ty = cfree_cg_type_ptr(p->c, u8_ty, 0); + toy_parser_advance(p); + dst = toy_parse_expr(p); + if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + src = toy_parse_expr(p); + if (src == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (!toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; + } else { + CfreeCgTypeId size_ty = toy_parse_expr(p); + if (size_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (size_ty != p->int_type) { + toy_error(p, p->cur.loc, "memory size must be i64"); + return CFREE_CG_TYPE_NONE; + } + dynamic_size = 1; + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_top_to_local(p, size_local, p->int_type); + } + if (!toy_parse_memory_align_operand(p, &align, &dynamic_align)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access_align(p, p->int_type, (uint32_t)align); + if (!toy_parse_mem_flags_tail(p, &access)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (dynamic_size || dynamic_align) { + if (!dynamic_size) { + size_local = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_const_to_local(p, size_local, p->int_type, (uint64_t)size); + } + src_local = cfree_cg_local(p->cg, u8_ptr_ty, toy_slot_attrs(0)); + cfree_cg_bitcast(p->cg, u8_ptr_ty); + toy_store_top_to_local(p, src_local, u8_ptr_ty); + dst_local = cfree_cg_local(p->cg, u8_ptr_ty, toy_slot_attrs(0)); + cfree_cg_bitcast(p->cg, u8_ptr_ty); + toy_store_top_to_local(p, dst_local, u8_ptr_ty); + toy_emit_dynamic_memory_loop(p, dst_local, src_local, size_local, 0, 0); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + cfree_cg_memmove(p->cg, (uint64_t)size, access, access); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "atomic_fence")) { + CfreeCgMemOrder order; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_mem_order(p, &order) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_atomic_fence(p->cg, order); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_atomic_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized) { + CfreeCgTypeId ty; + *recognized = 1; + + if (toy_sym_is(p, name, "atomic_load")) { + CfreeCgTypeId ptr_ty; + CfreeCgMemOrder order; + CfreeCgMemAccess access; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_mem_order(p, &order)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (!toy_parse_optional_access_arg(p, &access) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty) || + cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty) { + toy_error(p, p->cur.loc, "atomic_load pointer type mismatch"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_atomic_load(p->cg, access, order); + return ty; + } + + if (toy_sym_is(p, name, "atomic_store")) { + CfreeCgTypeId ptr_ty, val_ty; + CfreeCgMemOrder order; + CfreeCgMemAccess access; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + val_ty = toy_parse_expr(p); + if (val_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_mem_order(p, &order)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (!toy_parse_optional_access_arg(p, &access) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty) || + cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty || val_ty != ty) { + toy_error(p, p->cur.loc, "atomic_store type mismatch"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_atomic_store(p->cg, access, order); + return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + } + + if (toy_sym_is(p, name, "atomic_rmw")) { + CfreeCgAtomicOp op; + CfreeCgTypeId ptr_ty, val_ty; + CfreeCgMemOrder order; + CfreeCgMemAccess access; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || !toy_parse_atomic_op(p, &op) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + val_ty = toy_parse_expr(p); + if (val_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_mem_order(p, &order)) + return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (!toy_parse_optional_access_arg(p, &access) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty) || + cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty || val_ty != ty) { + toy_error(p, p->cur.loc, "atomic_rmw type mismatch"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_atomic_rmw(p->cg, access, op, order); + return ty; + } + + if (toy_sym_is(p, name, "atomic_cmpxchg")) { + CfreeCgTypeId ptr_ty, expected_ty, desired_ty, rec_ty; + CfreeCgMemOrder success_order, failure_order; + int weak; + CfreeCgMemAccess access; + CfreeCgField fields[2]; + CfreeCgLocal rec_slot; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + expected_ty = toy_parse_expr(p); + if (expected_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + desired_ty = toy_parse_expr(p); + if (desired_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || + !toy_parse_mem_order(p, &success_order) || !toy_expect_comma(p) || + !toy_parse_mem_order(p, &failure_order) || !toy_expect_comma(p) || + !toy_parse_cmpxchg_strength(p, &weak)) return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (!toy_parse_optional_access_arg(p, &access) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty) || + cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty || + expected_ty != ty || desired_ty != ty) { + toy_error(p, p->cur.loc, "atomic_cmpxchg type mismatch"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_atomic_cmpxchg(p->cg, access, success_order, failure_order, weak); + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(p->c, "prior"); + fields[0].type = ty; + fields[1].name = cfree_sym_intern(p->c, "ok"); + fields[1].type = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + rec_ty = cfree_cg_type_record(p->c, 0, fields, 2); + rec_slot = cfree_cg_local(p->cg, rec_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, rec_slot); + cfree_cg_field(p->cg, 1); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, fields[1].type)); + cfree_cg_push_local(p->cg, rec_slot); + cfree_cg_field(p->cg, 0); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, fields[0].type)); + cfree_cg_push_local(p->cg, rec_slot); + return rec_ty; + } + + if (toy_sym_is(p, name, "atomic_is_legal")) { + CfreeCgMemOrder order; + CfreeCgMemAccess access; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_mem_order(p, &order)) return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (!toy_parse_optional_access_arg(p, &access) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int(p->cg, + cfree_cg_atomic_is_legal(p->c, access, order) + ? 1u + : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + if (toy_sym_is(p, name, "atomic_is_lock_free")) { + CfreeCgMemAccess access; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + access = toy_mem_access(p, ty); + if (p->cur.kind != TOK_RPAREN && !toy_parse_access_group(p, &access)) + return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + cfree_cg_push_int( + p->cg, cfree_cg_atomic_is_lock_free(p->c, access) ? 1u : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_low_level_builtin_call(ToyParser* p, CfreeSym name, + int* recognized) { + *recognized = 1; + + if (toy_sym_is(p, name, "syscall")) { + uint32_t nargs = 0; + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + if (!toy_parser_match(p, TOK_RPAREN)) { + for (;;) { + CfreeCgTypeId arg_ty = toy_parse_expr(p); + if (arg_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_intlike(p, arg_ty) && !toy_type_is_ptr(p, arg_ty)) { + toy_error(p, p->cur.loc, + "syscall arguments must be integer or pointer"); + return CFREE_CG_TYPE_NONE; + } + nargs++; + if (nargs > 7u) { + toy_error(p, p->cur.loc, "too many syscall arguments"); + return CFREE_CG_TYPE_NONE; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + } + if (nargs == 0) { + toy_error(p, p->cur.loc, "syscall expects a syscall number"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "setjmp")) { + CfreeCgTypeId buf_ty; + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + buf_ty = toy_parse_expr(p); + if (buf_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, buf_ty)) { + toy_error(p, p->cur.loc, "setjmp expects pointer buffer"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "longjmp")) { + CfreeCgTypeId buf_ty, value_ty; + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + buf_ty = toy_parse_expr(p); + if (buf_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + value_ty = toy_parse_expr(p); + if (value_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, buf_ty) || !toy_type_is_intlike(p, value_ty)) { + toy_error(p, p->cur.loc, + "longjmp expects pointer buffer and integer value"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "irq_save") || toy_sym_is(p, name, "wfi") || + toy_sym_is(p, name, "wfe") || toy_sym_is(p, name, "sev")) { + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "irq_restore")) { + CfreeCgTypeId prev_ty; + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + prev_ty = toy_parse_expr(p); + if (prev_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_intlike(p, prev_ty)) { + toy_error(p, p->cur.loc, "irq_restore expects integer state"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "dmb") || toy_sym_is(p, name, "dsb")) { + CfreeCgBarrierScope scope; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_barrier_scope(p, &scope) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + (void)scope; + return toy_unsupported_intrinsic(p); + } + + if (toy_sym_is(p, name, "dcache_clean") || + toy_sym_is(p, name, "dcache_invalidate") || + toy_sym_is(p, name, "dcache_clean_invalidate") || + toy_sym_is(p, name, "icache_invalidate")) { + CfreeCgTypeId ptr_ty, size_ty; + if (!toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + ptr_ty = toy_parse_expr(p); + if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + size_ty = toy_parse_expr(p); + if (size_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, ptr_ty) || !toy_type_is_intlike(p, size_ty)) { + toy_error(p, p->cur.loc, + "cache intrinsic expects pointer and integer size"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} + +CfreeCgTypeId toy_parse_low_level_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized) { + CfreeCgTypeId ty; + *recognized = 1; + + if (toy_sym_is(p, name, "coro_switch")) { + CfreeCgTypeId from_ty, to_ty, value_ty; + if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || + !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; + from_ty = toy_parse_expr(p); + if (from_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + to_ty = toy_parse_expr(p); + if (to_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) + return CFREE_CG_TYPE_NONE; + value_ty = toy_parse_expr(p); + if (value_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_RPAREN)) + return CFREE_CG_TYPE_NONE; + if (!toy_type_is_ptr(p, from_ty) || !toy_type_is_ptr(p, to_ty) || + value_ty != ty) { + toy_error(p, p->cur.loc, + "coro_switch expects pointer contexts and matching value type"); + return CFREE_CG_TYPE_NONE; + } + return toy_unsupported_intrinsic(p); + } + + *recognized = 0; + return CFREE_CG_TYPE_NONE; +} diff --git a/lang/toy/compile.c b/lang/toy/compile.c @@ -0,0 +1,33 @@ +#include "toy.h" + +#include "internal.h" + +int cfree_toy_compile(CfreeCompiler* c, const CfreeCompileOptions* opts, + const CfreeBytesInput* input, CfreeObjBuilder* out) { + ToyParser p; + CfreeCg* cg; + const uint8_t* source; + + if (!c || !input || !out) return 1; + + source = input->data ? input->data : (const uint8_t*)""; + cg = cfree_cg_new(c, out, opts); + if (!cg) return 1; + + toy_parser_init(&p, c, cg, source, input->len); + if (!toy_parse_program(&p) || p.has_error) { + toy_parser_dispose(&p); + cfree_cg_free(cg); + return 1; + } + if (p.cur.kind != TOK_EOF) { + toy_error(&p, p.cur.loc, "unexpected token after program end"); + toy_parser_dispose(&p); + cfree_cg_free(cg); + return 1; + } + + toy_parser_dispose(&p); + cfree_cg_free(cg); + return 0; +} diff --git a/lang/toy/data.c b/lang/toy/data.c @@ -0,0 +1,507 @@ +#include "internal.h" + +#include <stdlib.h> +#include <string.h> + +int toy_parse_data_array_builtin(ToyParser* p, CfreeCgTypeId elem_ty, + uint64_t total_size, uint64_t* pos); + +int toy_parse_global_var(ToyParser* p, int is_extern, int is_pub) { + CfreeSym name; + CfreeCgTypeId ty; + CfreeCgDecl decl; + CfreeCgDataDefAttrs data_attrs; + CfreeCgSym sym; + ToyTypeId toy_type; + int mutable; + ToyAttrSet attrs; + CfreeSymBind default_bind = + (is_extern || is_pub) ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + + if (p->cur.kind == TOK_VAR) { + mutable = 1; + toy_parser_advance(p); + } else { + mutable = 0; + toy_parser_advance(p); /* let */ + } + if (!toy_parse_attr_list(p, &attrs, default_bind)) return 0; + if (!toy_validate_attr_placement( + p, &attrs, + TOY_ATTR_SYMBOL | TOY_ATTR_OBJECT | TOY_ATTR_SECTION | + TOY_ATTR_OBJECT_DATA | (is_extern ? 0u : TOY_ATTR_DATA), + is_extern ? "invalid extern data attribute" + : "invalid object attribute")) { + return 0; + } + + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after identifier"); + return 0; + } + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + toy_type = p->last_type; + if (attrs.is_common && !mutable) { + toy_error(p, p->cur.loc, "common object must be mutable"); + return 0; + } + if (attrs.is_common && p->cur.kind == TOK_EQ) { + toy_error(p, p->cur.loc, "common object cannot have initializer"); + return 0; + } + + memset(&decl, 0, sizeof(decl)); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = toy_c_linkage_name(p, name); + if (!decl.linkage_name) return 0; + decl.display_name = name; + decl.type = ty; + decl.sym = attrs.sym; + decl.as.object = attrs.object; + if (!mutable) decl.as.object.flags |= CFREE_CG_OBJ_READONLY; + + sym = cfree_cg_decl(p->cg, decl); + if (sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "failed to declare global"); + return 0; + } + + if (!toy_add_global_typed(p, name, sym, ty, toy_type, mutable)) { + return 0; + } + + data_attrs = attrs.data; + + if (is_extern) { + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after extern object"); + return 0; + } + return 1; + } + + if (toy_parser_match(p, TOK_EQ)) { + if (p->cur.kind == TOK_NUMBER) { + if (p->cur.is_float) { + if (!toy_type_is_float(p, ty)) { + toy_error(p, p->cur.loc, "float initializer requires float object"); + return 0; + } + cfree_cg_data_begin(p->cg, sym, data_attrs); + cfree_cg_data_float(p->cg, p->cur.float_value, ty); + cfree_cg_data_end(p->cg); + toy_parser_advance(p); + } else { + uint8_t buf[16]; + size_t n = (size_t)cfree_cg_type_size(p->c, ty); + if (n > sizeof buf) { + toy_error(p, p->cur.loc, "initializer too large"); + return 0; + } + toy_emit_int_bytes(p, (uint64_t)p->cur.int_value, buf, n); + toy_parser_advance(p); + cfree_cg_data_begin(p->cg, sym, data_attrs); + cfree_cg_data_bytes(p->cg, buf, n); + cfree_cg_data_end(p->cg); + } + } else if (p->cur.kind == TOK_AMPERSAND) { + CfreeSym target; + CfreeCgSym target_sym; + uint32_t nbytes = (uint32_t)cfree_cg_type_size(p->c, ty); + toy_parser_advance(p); + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier after '&'"); + return 0; + } + target = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + target_sym = toy_find_decl_sym(p, target); + if (target_sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "undefined symbol in initializer"); + return 0; + } + cfree_cg_data_begin(p->cg, sym, data_attrs); + cfree_cg_data_addr(p->cg, target_sym, 0, nbytes, 0); + cfree_cg_data_end(p->cg); + } else if (p->cur.kind == TOK_STRING && + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY && + cfree_cg_type_array_elem(p->c, ty) == + toy_builtin_type(p, CFREE_CG_BUILTIN_I8)) { + uint8_t buf[1024]; + size_t len = 0; + uint64_t size = cfree_cg_type_size(p->c, ty); + if (!toy_parse_string_bytes(p, buf, sizeof buf, &len)) return 0; + if ((uint64_t)len > size) { + toy_error(p, p->cur.loc, "string initializer too large"); + return 0; + } + cfree_cg_data_begin(p->cg, sym, data_attrs); + cfree_cg_data_bytes(p->cg, buf, len); + if ((uint64_t)len < size) cfree_cg_data_zero(p->cg, size - (uint64_t)len); + cfree_cg_data_end(p->cg); + } else if (p->cur.kind == TOK_LBRACKET && + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { + CfreeCgTypeId elem_ty = cfree_cg_type_array_elem(p->c, ty); + uint64_t total_size = cfree_cg_type_size(p->c, ty); + uint64_t elem_size = cfree_cg_type_size(p->c, elem_ty); + uint64_t pos = 0; + toy_parser_advance(p); + cfree_cg_data_begin(p->cg, sym, data_attrs); + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + if (pos >= total_size) { + toy_error(p, p->cur.loc, "too many array initializer elements"); + return 0; + } + if (p->cur.kind == TOK_AT) { + if (!toy_parse_data_array_builtin(p, elem_ty, total_size, &pos)) + return 0; + } else if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (pos + elem_size > total_size) { + toy_error(p, p->cur.loc, "too many array initializer elements"); + return 0; + } + cfree_cg_data_int(p->cg, (uint64_t)p->cur.int_value, elem_ty); + toy_parser_advance(p); + pos += elem_size; + } else { + toy_error(p, p->cur.loc, "expected constant array initializer"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after array initializer"); + return 0; + } + if (pos < total_size) cfree_cg_data_zero(p->cg, total_size - pos); + cfree_cg_data_end(p->cg); + } else if ((p->cur.kind == TOK_IDENT || p->cur.kind == TOK_LBRACE) && + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_RECORD) { + if (!toy_parse_global_record_initializer(p, sym, ty, data_attrs)) + return 0; + } else { + toy_error(p, p->cur.loc, "expected constant global initializer"); + return 0; + } + } else if (attrs.is_common) { + uint32_t align = data_attrs.align ? data_attrs.align + : cfree_cg_type_align(p->c, ty); + cfree_cg_data_common(p->cg, sym, cfree_cg_type_size(p->c, ty), align); + } else { + cfree_cg_data_begin(p->cg, sym, data_attrs); + cfree_cg_data_zero(p->cg, cfree_cg_type_size(p->c, ty)); + cfree_cg_data_end(p->cg); + } + + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';'"); + return 0; + } + return 1; +} + +int toy_parse_global_record_initializer(ToyParser* p, CfreeCgSym sym, + CfreeCgTypeId record_ty, + CfreeCgDataDefAttrs data_attrs) { + uint8_t* seen; + uint64_t pos = 0; + uint64_t total_size = cfree_cg_type_size(p->c, record_ty); + uint32_t nfields = cfree_cg_type_record_nfields(p->c, record_ty); + ToyNamedType* named = toy_find_named_type_by_type(p, record_ty); + int positional = named && named->kind == TOY_NAMED_TUPLE; + seen = (uint8_t*)calloc(nfields ? nfields : 1u, sizeof *seen); + if (!seen) { + toy_error(p, p->cur.loc, "out of memory growing record initializer"); + return 0; + } + if (p->cur.kind == TOK_IDENT) toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected record initializer"); + free(seen); + return 0; + } + cfree_cg_data_begin(p->cg, sym, data_attrs); + if (positional) { + uint32_t field_index = 0; + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeCgField field; + uint64_t field_off; + if (field_index >= nfields) { + toy_error(p, p->cur.loc, "too many tuple initializer elements"); + free(seen); + return 0; + } + if (cfree_cg_type_record_field(p->c, record_ty, field_index, &field, + &field_off) != 0) { + free(seen); + return 0; + } + if (field_off > pos) { + cfree_cg_data_zero(p->cg, field_off - pos); + pos = field_off; + } + if (p->cur.kind == TOK_AT) { + if (!toy_parse_data_array_builtin(p, field.type, total_size, &pos)) { + free(seen); + return 0; + } + } else if (p->cur.kind != TOK_NUMBER || p->cur.is_float) { + toy_error(p, p->cur.loc, "expected constant tuple initializer"); + free(seen); + return 0; + } else { + cfree_cg_data_int(p->cg, (uint64_t)p->cur.int_value, field.type); + pos += cfree_cg_type_size(p->c, field.type); + toy_parser_advance(p); + } + field_index++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after tuple initializer"); + free(seen); + return 0; + } + if (pos < total_size) cfree_cg_data_zero(p->cg, total_size - pos); + cfree_cg_data_end(p->cg); + free(seen); + return 1; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeSym field_name; + CfreeCgField field; + uint32_t field_index; + uint64_t field_off; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected field name"); + free(seen); + return 0; + } + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' in record initializer"); + free(seen); + return 0; + } + if (!toy_record_field_index(p, record_ty, field_name, &field_index, + &field)) { + toy_error(p, p->cur.loc, "unknown record field"); + free(seen); + return 0; + } + if (field_index >= nfields || seen[field_index]) { + toy_error(p, p->cur.loc, "duplicate record field initializer"); + free(seen); + return 0; + } + seen[field_index] = 1; + if (cfree_cg_type_record_field(p->c, record_ty, field_index, NULL, + &field_off) != 0) { + free(seen); + return 0; + } + if (field_off < pos) { + toy_error(p, p->cur.loc, "record initializer fields out of order"); + free(seen); + return 0; + } + if (field_off > pos) { + cfree_cg_data_zero(p->cg, field_off - pos); + pos = field_off; + } + if (p->cur.kind == TOK_AT) { + if (!toy_parse_data_array_builtin(p, field.type, total_size, &pos)) { + free(seen); + return 0; + } + } else if (p->cur.kind != TOK_NUMBER || p->cur.is_float) { + toy_error(p, p->cur.loc, "expected constant record initializer"); + free(seen); + return 0; + } else { + cfree_cg_data_int(p->cg, (uint64_t)p->cur.int_value, field.type); + pos += cfree_cg_type_size(p->c, field.type); + toy_parser_advance(p); + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after record initializer"); + free(seen); + return 0; + } + if (pos < total_size) cfree_cg_data_zero(p->cg, total_size - pos); + cfree_cg_data_end(p->cg); + free(seen); + return 1; +} + +int toy_parse_data_array_builtin(ToyParser* p, CfreeCgTypeId elem_ty, + uint64_t total_size, uint64_t* pos) { + CfreeSym name; + uint64_t elem_size = cfree_cg_type_size(p->c, elem_ty); + if (!toy_parser_expect(p, TOK_AT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected data initializer builtin"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "pcrel")) { + CfreeSym target_name; + CfreeCgSym target_sym; + int64_t addend; + if (!toy_type_is_intlike(p, elem_ty) || (elem_size != 4 && elem_size != 8)) { + toy_error(p, p->cur.loc, "pcrel requires i32/i64 initializer slot"); + return 0; + } + if (pos && *pos + elem_size > total_size) { + toy_error(p, p->cur.loc, "too many array initializer elements"); + return 0; + } + if (!toy_parser_expect(p, TOK_LPAREN) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected pcrel target symbol"); + return 0; + } + target_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COMMA) || + !toy_parse_number_arg(p, &addend) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "invalid pcrel initializer"); + return 0; + } + target_sym = toy_find_decl_sym(p, target_name); + if (target_sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "undefined pcrel target"); + return 0; + } + cfree_cg_data_pcrel(p->cg, target_sym, addend, (uint32_t)elem_size); + *pos += elem_size; + return 1; + } + if (toy_sym_is(p, name, "symdiff")) { + CfreeSym lhs_name; + CfreeSym rhs_name; + CfreeCgSym lhs_sym; + CfreeCgSym rhs_sym; + int64_t addend; + if (!toy_type_is_intlike(p, elem_ty) || elem_size == 0 || elem_size > 8) { + toy_error(p, p->cur.loc, "symdiff requires integer initializer slot"); + return 0; + } + if (pos && *pos + elem_size > total_size) { + toy_error(p, p->cur.loc, "too many array initializer elements"); + return 0; + } + if (!toy_parser_expect(p, TOK_LPAREN) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected symdiff lhs symbol"); + return 0; + } + lhs_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COMMA) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected symdiff rhs symbol"); + return 0; + } + rhs_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COMMA) || + !toy_parse_number_arg(p, &addend) || + !toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "invalid symdiff initializer"); + return 0; + } + lhs_sym = toy_find_decl_sym(p, lhs_name); + rhs_sym = toy_find_decl_sym(p, rhs_name); + if (lhs_sym == CFREE_CG_SYM_NONE || rhs_sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "undefined symdiff symbol"); + return 0; + } + cfree_cg_data_symdiff(p->cg, lhs_sym, rhs_sym, addend, + (uint32_t)elem_size); + *pos += elem_size; + return 1; + } + if (toy_sym_is(p, name, "labeladdr")) { + CfreeSym label_name; + ToyLabel* label; + if (cfree_cg_type_kind(p->c, elem_ty) != CFREE_CG_TYPE_PTR || + elem_size == 0 || elem_size > 8) { + toy_error(p, p->cur.loc, "labeladdr requires pointer initializer slot"); + return 0; + } + if (pos && *pos + elem_size > total_size) { + toy_error(p, p->cur.loc, "too many array initializer elements"); + return 0; + } + if (!toy_parser_expect(p, TOK_LPAREN) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected labeladdr label"); + return 0; + } + label_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_RPAREN)) return 0; + label = toy_find_label(p, label_name); + if (!label) { + toy_error(p, p->cur.loc, "unknown label"); + return 0; + } + cfree_cg_data_label_addr(p->cg, label->label, 0, (uint32_t)elem_size, 0); + *pos += elem_size; + return 1; + } + if (elem_size != 1) { + toy_error(p, p->cur.loc, "data initializer builtin requires byte array"); + return 0; + } + if (toy_sym_is(p, name, "pad")) { + int64_t size; + int64_t value; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_number_arg(p, &size) || + !toy_parser_expect(p, TOK_COMMA) || + !toy_parse_number_arg(p, &value) || + !toy_parser_expect(p, TOK_RPAREN)) { + return 0; + } + if (size < 0 || value < 0 || value > 255 || + *pos + (uint64_t)size > total_size) { + toy_error(p, p->cur.loc, "invalid data pad initializer"); + return 0; + } + cfree_cg_data_pad(p->cg, (uint64_t)size, (uint8_t)value); + *pos += (uint64_t)size; + return 1; + } + if (toy_sym_is(p, name, "align")) { + int64_t align; + uint64_t aligned; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_number_arg(p, &align) || + !toy_parser_expect(p, TOK_RPAREN)) { + return 0; + } + if (align <= 0 || (align & (align - 1)) != 0) { + toy_error(p, p->cur.loc, "invalid data alignment"); + return 0; + } + aligned = (*pos + (uint64_t)align - 1u) & ~((uint64_t)align - 1u); + if (aligned > total_size) { + toy_error(p, p->cur.loc, "data alignment exceeds initializer size"); + return 0; + } + cfree_cg_data_align(p->cg, (uint32_t)align); + *pos = aligned; + return 1; + } + toy_error(p, p->cur.loc, "unknown data initializer builtin"); + return 0; +} diff --git a/lang/toy/decls.c b/lang/toy/decls.c @@ -0,0 +1,511 @@ +#include "internal.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int toy_parse_type_alias_decl(ToyParser* p) { + CfreeSym name; + CfreeCgTypeId base; + if (!toy_parser_match(p, TOK_TYPE)) return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected type alias name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_EQ)) { + toy_error(p, p->cur.loc, "expected '=' in type alias"); + return 0; + } + base = toy_parse_type(p); + if (base == CFREE_CG_TYPE_NONE) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after type alias"); + return 0; + } + return toy_add_named_type(p, name, base, TOY_NAMED_ALIAS, base); +} + +int toy_parse_record_decl(ToyParser* p) { + CfreeSym name; + CfreeCgField* fields = NULL; + ToyRecordFieldInfo* field_infos = NULL; + size_t nfields = 0; + size_t cap_fields = 0; + size_t cap_field_infos = 0; + CfreeCgTypeId type; + ToyNamedType* named; + int packed = 0; + uint32_t record_align = 0; + int ok = 0; + + if (!toy_parser_match(p, TOK_RECORD)) return 0; + if (!toy_parse_record_attr_list(p, &packed, &record_align)) return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected record name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_parser_match(p, TOK_SEMI)) { + return toy_add_named_type(p, name, CFREE_CG_TYPE_NONE, TOY_NAMED_RECORD, + CFREE_CG_TYPE_NONE); + } + if (!toy_find_named_type(p, name)) { + if (!toy_add_named_type(p, name, CFREE_CG_TYPE_NONE, TOY_NAMED_RECORD, + CFREE_CG_TYPE_NONE)) { + return 0; + } + } + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after record name"); + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + if (!toy_parser_reserve(p, (void**)&fields, &cap_fields, nfields + 1u, + sizeof *fields, "record fields") || + !toy_parser_reserve(p, (void**)&field_infos, &cap_field_infos, + nfields + 1u, sizeof *field_infos, + "record field info")) { + goto done; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected record field name"); + goto done; + } + fields[nfields].name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parse_field_attr_list(p, &fields[nfields])) goto done; + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after record field name"); + goto done; + } + fields[nfields].type = toy_parse_type(p); + if (fields[nfields].type == CFREE_CG_TYPE_NONE) goto done; + field_infos[nfields].name = fields[nfields].name; + field_infos[nfields].storage_type = fields[nfields].type; + field_infos[nfields].toy_type = p->last_type; + nfields++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after record declaration"); + goto done; + } + if (packed) { + size_t i; + for (i = 0; i < nfields; ++i) { + if (!fields[i].align_override) fields[i].align_override = 1; + } + } + if (record_align) { + CfreeCgRecordDesc desc; + memset(&desc, 0, sizeof desc); + desc.tag = name; + desc.fields = fields; + desc.nfields = (uint32_t)nfields; + desc.align_override = record_align; + type = cfree_cg_type_record_ex(p->c, &desc); + } else { + type = cfree_cg_type_record(p->c, name, fields, (uint32_t)nfields); + } + if (type == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "failed to create record type"); + goto done; + } + if (!toy_add_named_type(p, name, type, TOY_NAMED_RECORD, type)) goto done; + named = toy_find_named_type(p, name); + ok = named && toy_set_named_type_fields(p, named, field_infos, nfields); + +done: + free(fields); + free(field_infos); + return ok; +} + +int toy_parse_tuple_decl(ToyParser* p) { + CfreeSym name; + CfreeCgField* fields = NULL; + ToyRecordFieldInfo* field_infos = NULL; + size_t nfields = 0; + size_t cap_fields = 0; + size_t cap_field_infos = 0; + CfreeCgTypeId type; + ToyNamedType* named; + char field_name_buf[16]; + int ok = 0; + + if (!toy_parser_match(p, TOK_TUPLE)) return 0; + if (!toy_skip_attr_list(p)) return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected tuple name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after tuple name"); + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + if (!toy_parser_reserve(p, (void**)&fields, &cap_fields, nfields + 1u, + sizeof *fields, "tuple fields") || + !toy_parser_reserve(p, (void**)&field_infos, &cap_field_infos, + nfields + 1u, sizeof *field_infos, + "tuple field info")) { + goto done; + } + snprintf(field_name_buf, sizeof field_name_buf, "%u", (uint32_t)nfields); + fields[nfields].name = cfree_sym_intern(p->c, field_name_buf); + fields[nfields].type = toy_parse_type(p); + if (fields[nfields].type == CFREE_CG_TYPE_NONE) goto done; + field_infos[nfields].name = fields[nfields].name; + field_infos[nfields].storage_type = fields[nfields].type; + field_infos[nfields].toy_type = p->last_type; + nfields++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after tuple declaration"); + goto done; + } + type = cfree_cg_type_record(p->c, name, fields, (uint32_t)nfields); + if (type == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "failed to create tuple type"); + goto done; + } + if (!toy_add_named_type(p, name, type, TOY_NAMED_TUPLE, type)) goto done; + named = toy_find_named_type(p, name); + ok = named && toy_set_named_type_fields(p, named, field_infos, nfields); + +done: + free(fields); + free(field_infos); + return ok; +} + +int toy_parse_enum_decl(ToyParser* p) { + CfreeSym name; + CfreeCgTypeId base; + CfreeCgEnumValue* values = NULL; + ToyEnumConst* toy_values = NULL; + size_t nvalues = 0; + size_t cap_values = 0; + size_t cap_toy_values = 0; + CfreeCgTypeId type; + ToyNamedType* named; + int ok = 0; + + if (!toy_parser_match(p, TOK_ENUM)) return 0; + if (!toy_skip_attr_list(p)) return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected enum name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected enum base type"); + return 0; + } + base = toy_parse_type(p); + if (base == CFREE_CG_TYPE_NONE) return 0; + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after enum base type"); + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + if (!toy_parser_reserve(p, (void**)&values, &cap_values, nvalues + 1u, + sizeof *values, "enum values") || + !toy_parser_reserve(p, (void**)&toy_values, &cap_toy_values, + nvalues + 1u, sizeof *toy_values, + "toy enum values")) { + goto done; + } + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected enum value"); + goto done; + } + values[nvalues].name = toy_tok_sym(p, p->cur); + toy_values[nvalues].name = values[nvalues].name; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_EQ) || p->cur.kind != TOK_NUMBER || + p->cur.is_float) { + toy_error(p, p->cur.loc, "expected enum integer value"); + goto done; + } + values[nvalues].value = p->cur.int_value; + toy_values[nvalues].value = p->cur.int_value; + toy_parser_advance(p); + nvalues++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after enum declaration"); + goto done; + } + type = cfree_cg_type_enum(p->c, name, base, values, (uint32_t)nvalues); + if (type == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "failed to create enum type"); + goto done; + } + if (!toy_add_named_type(p, name, type, TOY_NAMED_ENUM, base)) goto done; + named = toy_find_named_type(p, name); + ok = named && toy_set_named_type_enum_values(p, named, toy_values, nvalues); + +done: + free(values); + free(toy_values); + return ok; +} + +int toy_parse_alias_decl(ToyParser* p, int is_pub) { + CfreeSym name; + CfreeSym target_name; + CfreeCgSym target_sym; + CfreeCgAlias alias; + ToyAttrSet attrs; + CfreeSymBind default_bind = is_pub ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + if (!toy_parser_match(p, TOK_ALIAS)) return 0; + if (!toy_parse_attr_list(p, &attrs, default_bind)) return 0; + if (!toy_validate_attr_placement(p, &attrs, TOY_ATTR_SYMBOL, + "invalid alias attribute")) + return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected alias name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_EQ) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected alias target"); + return 0; + } + target_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + target_sym = toy_find_decl_sym(p, target_name); + if (target_sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "undefined alias target"); + return 0; + } + memset(&alias, 0, sizeof alias); + alias.linkage_name = toy_c_linkage_name(p, name); + if (!alias.linkage_name) return 0; + alias.display_name = name; + alias.target = target_sym; + alias.sym = attrs.sym; + if (cfree_cg_alias(p->cg, alias) == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "failed to define alias"); + return 0; + } + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after alias"); + return 0; + } + return 1; +} + +int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { + CfreeSym name; + CfreeCgTypeId ret_type; + CfreeCgTypeId* param_types = NULL; + ToyTypeId* param_toy_types = NULL; + CfreeCgAbiAttrs* param_attrs = NULL; + CfreeCgAbiAttrs ret_attrs; + CfreeCgFuncParam* sig_params = NULL; + CfreeSym* param_names = NULL; + size_t nparams = 0; + size_t cap_param_types = 0; + size_t cap_param_toy_types = 0; + size_t cap_param_attrs = 0; + size_t cap_sig_params = 0; + size_t cap_param_names = 0; + int variadic = 0; + CfreeCgDecl decl; + CfreeCgFuncSig sig; + CfreeCgTypeId fn_ty; + ToyFn* fn_entry; + ToyAttrSet attrs; + ToyTypeId fn_toy_type; + ToyTypeId ret_toy_type; + size_t i; + CfreeSymBind default_bind = + (is_extern || is_pub) ? CFREE_SB_GLOBAL : CFREE_SB_LOCAL; + + if (!toy_parser_match(p, TOK_FN)) return 0; + if (!toy_parse_attr_list(p, &attrs, default_bind)) return -1; + if (!toy_validate_attr_placement( + p, &attrs, TOY_ATTR_SYMBOL | TOY_ATTR_FUNC | TOY_ATTR_SECTION, + "invalid function attribute")) + return -1; + memset(&ret_attrs, 0, sizeof ret_attrs); + + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected function name"); + return -1; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + + if (!toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected '(' after function name"); + return -1; + } + + if (p->cur.kind != TOK_RPAREN) { + for (;;) { + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected parameter name"); + return -1; + } + if (!toy_parser_reserve(p, (void**)&param_types, &cap_param_types, + nparams + 1u, sizeof *param_types, + "function parameter types") || + !toy_parser_reserve(p, (void**)&param_attrs, &cap_param_attrs, + nparams + 1u, sizeof *param_attrs, + "function parameter attrs") || + !toy_parser_reserve(p, (void**)&param_toy_types, + &cap_param_toy_types, nparams + 1u, + sizeof *param_toy_types, + "function parameter source types") || + !toy_parser_reserve(p, (void**)&sig_params, &cap_sig_params, + nparams + 1u, sizeof *sig_params, + "function signature parameters") || + !toy_parser_reserve(p, (void**)&param_names, &cap_param_names, + nparams + 1u, sizeof *param_names, + "function parameter names")) { + return -1; + } + param_names[nparams] = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parse_abi_attr_list(p, &param_attrs[nparams])) return -1; + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after parameter name"); + return -1; + } + param_types[nparams] = toy_parse_type(p); + if (param_types[nparams] == CFREE_CG_TYPE_NONE) return -1; + param_toy_types[nparams] = p->last_type; + nparams++; + if (p->cur.kind == TOK_COMMA) { + toy_parser_advance(p); + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } + } else { + break; + } + } + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after parameters"); + return -1; + } + + if (toy_parser_match(p, TOK_COLON)) { + ret_type = toy_parse_type(p); + if (ret_type == CFREE_CG_TYPE_NONE) return -1; + ret_toy_type = p->last_type; + if (!toy_parse_abi_attr_list(p, &ret_attrs)) return -1; + } else { + ret_type = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + ret_toy_type = toy_type_from_cg(p, ret_type); + } + + for (i = 0; i < nparams; i++) { + sig_params[i].type = param_types[i]; + sig_params[i].attrs = param_attrs[i]; + } + memset(&sig, 0, sizeof sig); + sig.ret = ret_type; + sig.ret_attrs = ret_attrs; + sig.params = sig_params; + sig.nparams = (uint32_t)nparams; + sig.call_conv = attrs.has_call_conv ? attrs.call_conv : CFREE_CG_CC_TARGET_C; + sig.abi_variadic = variadic; + + fn_ty = cfree_cg_type_func(p->c, sig); + if (fn_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "failed to create function type"); + return -1; + } + fn_toy_type = toy_type_register_func(p, fn_ty, ret_toy_type, + param_toy_types, nparams, variadic); + + memset(&decl, 0, sizeof(decl)); + decl.kind = CFREE_CG_DECL_FUNC; + decl.linkage_name = toy_c_linkage_name(p, name); + if (!decl.linkage_name) return -1; + decl.display_name = name; + decl.type = fn_ty; + decl.sym = attrs.sym; + if (toy_sym_is(p, name, "main")) decl.sym.bind = CFREE_SB_GLOBAL; + decl.as.func = attrs.func; + + { + CfreeCgSym sym = cfree_cg_decl(p->cg, decl); + if (sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "failed to declare function"); + return -1; + } + fn_entry = toy_add_fn_typed(p, name, sym, fn_ty, fn_toy_type, ret_type, + ret_toy_type, param_types, param_toy_types, + nparams, variadic); + } + if (!fn_entry) { + toy_error(p, p->cur.loc, "failed to declare function"); + return -1; + } + + if (is_extern) { + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after extern function"); + return -1; + } + free(param_types); + free(param_toy_types); + free(param_attrs); + free(sig_params); + free(param_names); + return 1; + } + + cfree_cg_func_begin(p->cg, fn_entry->sym); + + p->nvars = 0; + p->nlabels = 0; + p->cur_fn_ret = ret_type; + p->cur_fn_ret_toy = ret_toy_type; + for (i = 0; i < nparams; i++) { + CfreeCgLocal param = cfree_cg_param( + p->cg, (uint32_t)i, param_types[i], toy_slot_attrs(param_names[i])); + if (!toy_add_local_typed(p, param_names[i], param_types[i], + param_toy_types[i], param, 1)) return -1; + } + + if (!toy_parse_block(p)) return -1; + + if (ret_type == toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { + cfree_cg_ret_void(p->cg); + } + + cfree_cg_func_end(p->cg); + p->nvars = 0; + p->nlabels = 0; + p->cur_fn_ret = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + p->cur_fn_ret_toy = toy_type_from_cg(p, p->cur_fn_ret); + free(param_types); + free(param_toy_types); + free(param_attrs); + free(sig_params); + free(param_names); + return 1; +} diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -0,0 +1,1335 @@ +#include "internal.h" + +#include <cfree/cg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Public CG API coverage goals for this frontend are tracked in + * doc/toy-todo.md. Keep this file aligned with include/cfree/cg.h rather than + * private CG implementation details. */ + +/* ============================================================ + * Parser (single-pass parse -> codegen) + * ============================================================ */ + +/* ============================================================ + * Expression parsing + * ============================================================ */ + +CfreeCgTypeId toy_parse_expr(ToyParser* p); +CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type); +CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name); + +static void toy_note_cg_result_type(ToyParser* p, CfreeCgTypeId ty) { + p->last_type = toy_type_from_cg(p, ty); +} + +static void toy_note_builtin_result_type(ToyParser* p, CfreeCgTypeId ty) { + CfreeCgTypeId last_cg = toy_type_resolved_cg(p, p->last_type); + if (last_cg != ty) toy_note_cg_result_type(p, ty); +} + +CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) { + ToyVar* v = toy_find_var(p, name); + if (v) { + toy_push_var_lvalue(p, v); + cfree_cg_load(p->cg, toy_mem_access(p, v->type)); + p->last_type = v->toy_type; + return v->type; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + cfree_cg_load(p->cg, toy_mem_access(p, g->type)); + p->last_type = g->toy_type; + return g->type; + } + } + { + ToyFn* fn = toy_find_fn(p, name); + if (fn) { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, fn->type, 0); + cfree_cg_push_symbol_addr(p->cg, fn->sym, 0); + p->last_type = toy_type_register_ptr(p, ptr_ty, fn->toy_type, 0); + return ptr_ty; + } + } + return CFREE_CG_TYPE_NONE; +} + +int toy_parse_call_args(ToyParser* p, ToyToken call_tok, + CfreeCgTypeId fn_ty, const ToyTypeId* toy_params, + size_t toy_nparams, size_t* out_nargs) { + uint32_t nparams = cfree_cg_type_func_nparams(p->c, fn_ty); + int variadic = cfree_cg_type_func_is_variadic(p->c, fn_ty); + size_t nargs = 0; + if (p->cur.kind != TOK_RPAREN) { + for (;;) { + CfreeCgTypeId arg_ty; + if (nargs < nparams && p->cur.kind == TOK_DOT) { + CfreeCgFuncParam param = + cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs); + ToyNamedType* named = toy_find_named_type_by_type(p, param.type); + CfreeSym value_name; + size_t i; + int found = 0; + int64_t value = 0; + toy_parser_advance(p); /* . */ + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected enum value"); + return 0; + } + value_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!named || named->kind != TOY_NAMED_ENUM) { + toy_error(p, p->cur.loc, "dot argument requires enum parameter"); + return 0; + } + for (i = 0; i < named->nenum_values; ++i) { + if (named->enum_values[i].name == value_name) { + found = 1; + value = named->enum_values[i].value; + break; + } + } + if (!found) { + toy_error(p, p->cur.loc, "unknown enum value"); + return 0; + } + cfree_cg_push_int(p->cg, (uint64_t)value, param.type); + arg_ty = param.type; + p->last_type = + (toy_params && nargs < toy_nparams) ? toy_params[nargs] + : toy_type_from_cg(p, arg_ty); + } else { + arg_ty = toy_parse_expr(p); + } + if (arg_ty == CFREE_CG_TYPE_NONE) return 0; + if (nargs < nparams) { + CfreeCgFuncParam param = + cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs); + ToyTypeId expected = + (toy_params && nargs < toy_nparams) ? toy_params[nargs] + : TOY_TYPE_NONE; + ToyTypeId actual = p->last_type; + if (expected != TOY_TYPE_NONE && + !toy_type_accepts_type(p, expected, actual)) { + toy_error(p, call_tok.loc, "function argument type mismatch"); + return 0; + } + if (expected == TOY_TYPE_NONE && arg_ty != param.type) { + toy_error(p, call_tok.loc, "function argument type mismatch"); + return 0; + } + } else if (!variadic) { + toy_error(p, call_tok.loc, "too many arguments"); + return 0; + } + nargs++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after arguments"); + return 0; + } + if (nargs < nparams) { + toy_error(p, call_tok.loc, "too few arguments"); + return 0; + } + *out_nargs = nargs; + return 1; +} + +CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type) { + CfreeCgMemAccess access; + (void)p; + memset(&access, 0, sizeof access); + access.type = type; + return access; +} + +static int toy_emit_cast(ToyParser* p, CfreeCgTypeId src, CfreeCgTypeId dst) { + CfreeCgTypeKind sk, dk; + if (src == dst) return 1; + sk = cfree_cg_type_kind(p->c, src); + dk = cfree_cg_type_kind(p->c, dst); + + if (dk == CFREE_CG_TYPE_BOOL && toy_type_is_intlike(p, src)) { + cfree_cg_push_int(p->cg, 0, src); + cfree_cg_int_cmp(p->cg, CFREE_CG_INT_NE); + return 1; + } + if (toy_type_is_intlike(p, src) && toy_type_is_intlike(p, dst)) { + uint32_t sw = toy_type_int_width(p, src); + uint32_t dw = toy_type_int_width(p, dst); + if (dw > sw && sk == CFREE_CG_TYPE_BOOL) cfree_cg_zext(p->cg, dst); + else if (dw > sw) cfree_cg_sext(p->cg, dst); + else if (dw < sw) cfree_cg_trunc(p->cg, dst); + return 1; + } + if (toy_type_is_intlike(p, src) && dk == CFREE_CG_TYPE_FLOAT) { + cfree_cg_sint_to_float(p->cg, dst, CFREE_CG_ROUND_DEFAULT); + return 1; + } + if (sk == CFREE_CG_TYPE_FLOAT && toy_type_is_intlike(p, dst)) { + cfree_cg_float_to_sint(p->cg, dst, CFREE_CG_ROUND_TOWARD_ZERO); + return 1; + } + if (sk == CFREE_CG_TYPE_FLOAT && dk == CFREE_CG_TYPE_FLOAT) { + uint32_t sw = cfree_cg_type_float_width(p->c, src); + uint32_t dw = cfree_cg_type_float_width(p->c, dst); + if (dw > sw) cfree_cg_fpext(p->cg, dst); + else if (dw < sw) cfree_cg_fptrunc(p->cg, dst); + return 1; + } + if (sk == CFREE_CG_TYPE_PTR && toy_type_is_intlike(p, dst)) { + cfree_cg_ptr_to_int(p->cg, dst); + return 1; + } + if (toy_type_is_intlike(p, src) && dk == CFREE_CG_TYPE_PTR) { + cfree_cg_int_to_ptr(p->cg, dst); + return 1; + } + if (sk == CFREE_CG_TYPE_PTR && dk == CFREE_CG_TYPE_PTR) { + cfree_cg_bitcast(p->cg, dst); + return 1; + } + toy_error(p, p->cur.loc, "unsupported cast"); + return 0; +} + +CfreeCgLocalAttrs toy_slot_attrs(CfreeSym name) { + CfreeCgLocalAttrs attrs; + memset(&attrs, 0, sizeof attrs); + attrs.name = name; + return attrs; +} + +CfreeSym toy_c_linkage_name(ToyParser* p, CfreeSym source_name) { + CfreeSym linkage_name = cfree_cg_c_linkage_name(p->c, source_name); + if (!linkage_name) { + toy_error(p, p->cur.loc, "failed to create linkage name"); + } + return linkage_name; +} + +void toy_inline_asm(ToyParser* p, CfreeSym tmpl, + const CfreeCgAsmOperand* outputs, uint32_t noutputs, + const CfreeCgAsmOperand* inputs, uint32_t ninputs, + const CfreeSym* clobbers, uint32_t nclobbers, + uint32_t flags, uint32_t clobber_abi_sets) { + CfreeCgInlineAsm asm_block; + memset(&asm_block, 0, sizeof asm_block); + asm_block.tmpl = tmpl; + asm_block.outputs = outputs; + asm_block.noutputs = noutputs; + asm_block.inputs = inputs; + asm_block.ninputs = ninputs; + asm_block.clobbers = clobbers; + asm_block.nclobbers = nclobbers; + asm_block.flags = flags; + asm_block.clobber_abi_sets = clobber_abi_sets; + cfree_cg_inline_asm(p->cg, asm_block); +} + +int toy_emit_truthy(ToyParser* p, CfreeCgTypeId type) { + if (type == toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)) return 1; + if (toy_type_is_intlike(p, type)) { + cfree_cg_push_int(p->cg, 0, type); + cfree_cg_int_cmp(p->cg, CFREE_CG_INT_NE); + return 1; + } + toy_error(p, p->cur.loc, "condition must be int or bool"); + return 0; +} + +static int toy_note_expr_island(ToyParser* p, ToyExprIsland island) { + uint32_t prior = p->expr_island_mask; + if (prior && prior != (uint32_t)island) { + toy_error(p, p->cur.loc, "mixed precedence islands require parentheses"); + return 0; + } + p->expr_island_mask |= (uint32_t)island; + return 1; +} + +int toy_expect_comma(ToyParser* p) { + if (!toy_parser_expect(p, TOK_COMMA)) { + toy_error(p, p->cur.loc, "expected ','"); + return 0; + } + return 1; +} + +static int toy_parse_dot_constant_value(ToyParser* p, int64_t* out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected dot constant"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "arm64")) *out = 1; + else if (toy_sym_is(p, name, "x64")) *out = 2; + else if (toy_sym_is(p, name, "rv64")) *out = 3; + else if (toy_sym_is(p, name, "x86")) *out = 4; + else if (toy_sym_is(p, name, "arm32")) *out = 5; + else if (toy_sym_is(p, name, "rv32")) *out = 6; + else if (toy_sym_is(p, name, "wasm")) *out = 7; + else { + toy_error(p, p->cur.loc, "unknown dot constant"); + return 0; + } + return 1; +} + +int toy_parse_switch_label_value(ToyParser* p, CfreeCgTypeId selector_ty, + int64_t* out) { + ToyNamedType* named; + CfreeSym value_name; + size_t i; + if (p->cur.kind != TOK_DOT) return toy_parse_number_arg(p, out); + named = toy_find_named_type_by_type(p, selector_ty); + if (!named || named->kind != TOY_NAMED_ENUM) + return toy_parse_dot_constant_value(p, out); + toy_parser_advance(p); /* . */ + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected enum value"); + return 0; + } + value_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + for (i = 0; i < named->nenum_values; ++i) { + if (named->enum_values[i].name == value_name) { + *out = named->enum_values[i].value; + return 1; + } + } + toy_error(p, p->cur.loc, "unknown enum value"); + return 0; +} + +int toy_parse_symbol_feature_const(ToyParser* p, CfreeCgSymbolFeature* out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected symbol feature"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "weak")) *out = CFREE_CG_SYMFEAT_WEAK; + else if (toy_sym_is(p, name, "protected_visibility")) + *out = CFREE_CG_SYMFEAT_PROTECTED_VISIBILITY; + else if (toy_sym_is(p, name, "dllimport")) + *out = CFREE_CG_SYMFEAT_DLLIMPORT; + else if (toy_sym_is(p, name, "dllexport")) + *out = CFREE_CG_SYMFEAT_DLLEXPORT; + else if (toy_sym_is(p, name, "comdat")) *out = CFREE_CG_SYMFEAT_COMDAT; + else if (toy_sym_is(p, name, "common")) *out = CFREE_CG_SYMFEAT_COMMON; + else if (toy_sym_is(p, name, "merge_sections")) + *out = CFREE_CG_SYMFEAT_MERGE_SECTIONS; + else if (toy_sym_is(p, name, "constructor_priority")) + *out = CFREE_CG_SYMFEAT_CONSTRUCTOR_PRIORITY; + else if (toy_sym_is(p, name, "tls_local_exec")) + *out = CFREE_CG_SYMFEAT_TLS_LOCAL_EXEC; + else if (toy_sym_is(p, name, "tls_initial_exec")) + *out = CFREE_CG_SYMFEAT_TLS_INITIAL_EXEC; + else if (toy_sym_is(p, name, "tls_local_dynamic")) + *out = CFREE_CG_SYMFEAT_TLS_LOCAL_DYNAMIC; + else if (toy_sym_is(p, name, "tls_general_dynamic")) + *out = CFREE_CG_SYMFEAT_TLS_GENERAL_DYNAMIC; + else { + toy_error(p, p->cur.loc, "unknown symbol feature"); + return 0; + } + return 1; +} + +int toy_parse_backend_feature_const(ToyParser* p, uint64_t* out) { + CfreeSym name; + if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected backend feature"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (toy_sym_is(p, name, "unaligned_memory")) + *out = CFREE_CG_BACKEND_UNALIGNED_MEMORY; + else if (toy_sym_is(p, name, "strict_alignment")) + *out = CFREE_CG_BACKEND_STRICT_ALIGNMENT; + else if (toy_sym_is(p, name, "red_zone")) + *out = CFREE_CG_BACKEND_RED_ZONE; + else if (toy_sym_is(p, name, "simd")) + *out = CFREE_CG_BACKEND_SIMD; + else if (toy_sym_is(p, name, "pointer_auth")) + *out = CFREE_CG_BACKEND_POINTER_AUTH; + else if (toy_sym_is(p, name, "branch_protection")) + *out = CFREE_CG_BACKEND_BRANCH_PROTECTION; + else { + toy_error(p, p->cur.loc, "unknown backend feature"); + return 0; + } + return 1; +} + +int toy_parse_rounding_const(ToyParser* p, CfreeCgRounding* out) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (toy_sym_is(p, name, "default")) *out = CFREE_CG_ROUND_DEFAULT; + else if (toy_sym_is(p, name, "nearest_even")) + *out = CFREE_CG_ROUND_NEAREST_EVEN; + else if (toy_sym_is(p, name, "toward_zero")) + *out = CFREE_CG_ROUND_TOWARD_ZERO; + else if (toy_sym_is(p, name, "down")) *out = CFREE_CG_ROUND_DOWN; + else if (toy_sym_is(p, name, "up")) *out = CFREE_CG_ROUND_UP; + else { + toy_error(p, p->cur.loc, "unknown rounding mode"); + return 0; + } + return 1; +} + +int toy_validate_bit_range(ToyParser* p, CfreeCgTypeId ty, int64_t lo, + int64_t width, int allow_full_width) { + uint32_t type_width; + if (!toy_type_is_intlike(p, ty) || lo < 0 || width <= 0) return 0; + type_width = toy_type_int_width(p, ty); + if (width > (int64_t)type_width) return 0; + if (!allow_full_width && width >= 64) return 0; + if (lo > (int64_t)type_width || width > (int64_t)type_width - lo) return 0; + return 1; +} + +int toy_target_code(ToyParser* p) { + switch (p->target.arch) { + case CFREE_ARCH_ARM_64: + return 1; + case CFREE_ARCH_X86_64: + return 2; + case CFREE_ARCH_RV64: + return 3; + default: + return 0; + } +} + +int toy_parse_arch_string(ToyParser* p, CfreeSym* out, size_t* len_out) { + if (p->cur.kind == TOK_STRING) return toy_parse_string_sym(p, out, len_out); + + toy_error(p, p->cur.loc, "expected asm template"); + return 0; +} + +static int toy_emit_var_addr(ToyParser* p, CfreeSym name) { + ToyVar* v = toy_find_var(p, name); + if (v) { + toy_push_var_addr(p, v); + return 1; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol_addr(p->cg, g->sym, 0); + return 1; + } + } + return 0; +} + +CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name) { + ToyVar* v = toy_find_var(p, name); + if (v) { + toy_push_var_lvalue(p, v); + return v->type; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + return g->type; + } + } + return CFREE_CG_TYPE_NONE; +} + +int toy_parse_va_list_addr_arg(ToyParser* p) { + CfreeSym name; + ToyVar* v; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected va_list identifier"); + return 0; + } + name = toy_tok_sym(p, p->cur); + v = toy_find_var(p, name); + if (!v || v->type != p->va_list_type) { + toy_error(p, p->cur.loc, "expected va_list local"); + return 0; + } + toy_parser_advance(p); + return toy_emit_var_addr(p, name); +} + +static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { + toy_set_loc(p); + if (p->cur.kind == TOK_AT) { + CfreeSym name; + ToyToken at_tok = p->cur; + toy_parser_advance(p); + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected builtin name after '@'"); + return CFREE_CG_TYPE_NONE; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + + if (p->cur.kind == TOK_LT) { + int recognized = 0; + CfreeCgTypeId builtin_ty = + toy_parse_generic_builtin(p, name, &recognized); + if (recognized) { + if (builtin_ty != CFREE_CG_TYPE_NONE) + toy_note_builtin_result_type(p, builtin_ty); + return builtin_ty; + } + } + + if (p->cur.kind == TOK_LPAREN) { + int recognized = 0; + CfreeCgTypeId builtin_ty = toy_parse_builtin_call(p, name, &recognized); + if (recognized) { + if (builtin_ty != CFREE_CG_TYPE_NONE) + toy_note_builtin_result_type(p, builtin_ty); + return builtin_ty; + } + } + + toy_error(p, at_tok.loc, "unknown builtin"); + return CFREE_CG_TYPE_NONE; + } + + if (p->cur.kind == TOK_NUMBER) { + if (p->cur.is_float) { + cfree_cg_push_float(p->cg, p->cur.float_value, + toy_builtin_type(p, CFREE_CG_BUILTIN_F64)); + toy_parser_advance(p); + p->last_type = + toy_type_from_cg(p, toy_builtin_type(p, CFREE_CG_BUILTIN_F64)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_F64); + } + cfree_cg_push_int(p->cg, p->cur.int_value, p->int_type); + toy_parser_advance(p); + p->last_type = toy_type_from_cg(p, p->int_type); + return p->int_type; + } + + if (p->cur.kind == TOK_IDENT) { + CfreeSym name = toy_tok_sym(p, p->cur); + ToyToken ident_tok = p->cur; + toy_parser_advance(p); + + if (toy_sym_is(p, name, "true") || toy_sym_is(p, name, "false")) { + cfree_cg_push_int(p->cg, toy_sym_is(p, name, "true") ? 1u : 0u, + toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + p->last_type = toy_type_from_cg( + p, toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); + return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + } + + if (toy_sym_is(p, name, "NULL")) { + cfree_cg_push_int(p->cg, 0, p->int_type); + p->last_type = TOY_TYPE_NONE; + return p->int_type; + } + + if (p->cur.kind == TOK_LT) { + int recognized = 0; + CfreeCgTypeId builtin_ty = + toy_parse_generic_builtin(p, name, &recognized); + if (recognized) return builtin_ty; + } + + if (p->cur.kind == TOK_LPAREN) { + /* Function call */ + int recognized = 0; + CfreeCgTypeId builtin_ty = toy_parse_builtin_call(p, name, &recognized); + if (recognized) return builtin_ty; + + ToyFn* fn = toy_find_fn(p, name); + toy_parser_advance(p); /* ( */ + + size_t nargs = 0; + if (fn) { + if (!toy_parse_call_args(p, ident_tok, fn->type, fn->toy_params, + fn->nparams, &nargs)) + return CFREE_CG_TYPE_NONE; + cfree_cg_call_symbol_default(p->cg, fn->sym, (uint32_t)nargs); + p->last_type = fn->toy_ret; + return fn->ret; + } + + CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name); + ToyTypeId callee_toy_type = p->last_type; + const ToyType* source_fn_type = NULL; + if (callee_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, ident_tok.loc, "undefined function '%s'", + (const char*)ident_tok.text); + return CFREE_CG_TYPE_NONE; + } + CfreeCgTypeId fn_ty = toy_ptr_pointee_func_type(p, callee_ty); + if (fn_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, ident_tok.loc, "callee is not a function pointer"); + return CFREE_CG_TYPE_NONE; + } + source_fn_type = toy_type_get(p, toy_type_pointee(p, callee_toy_type)); + if (source_fn_type && source_fn_type->kind != TOY_TYPE_FUNC) + source_fn_type = NULL; + if (!toy_parse_call_args(p, ident_tok, fn_ty, + source_fn_type ? source_fn_type->params : NULL, + source_fn_type ? source_fn_type->nparams : 0, + &nargs)) + return CFREE_CG_TYPE_NONE; + cfree_cg_call_default(p->cg, (uint32_t)nargs, fn_ty); + p->last_type = + source_fn_type ? source_fn_type->ret + : toy_type_from_cg(p, + cfree_cg_type_func_ret(p->c, fn_ty)); + return cfree_cg_type_func_ret(p->c, fn_ty); + } + + if (p->cur.kind == TOK_LBRACKET || p->cur.kind == TOK_DOT) { + ToyVar* v = toy_find_var(p, name); + if (v && (cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD)) { + toy_push_var_lvalue(p, v); + return v->type; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g && + (cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD)) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + return g->type; + } + } + } + + { + CfreeCgTypeId ty = toy_push_named_rvalue(p, name); + if (ty != CFREE_CG_TYPE_NONE) return ty; + } + toy_error(p, ident_tok.loc, "undefined variable '%s'", + (const char*)ident_tok.text); + return CFREE_CG_TYPE_NONE; + } + + if (p->cur.kind == TOK_LPAREN) { + toy_parser_advance(p); + CfreeCgTypeId ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')'"); + return CFREE_CG_TYPE_NONE; + } + return ty; + } + + toy_error(p, p->cur.loc, "expected expression"); + return CFREE_CG_TYPE_NONE; +} + +static CfreeCgTypeId toy_parse_expr_postfix(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_primary(p); + ToyTypeId toy_ty = p->last_type; + while (ty != CFREE_CG_TYPE_NONE) { + if (toy_parser_match(p, TOK_DOTSTAR)) { + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_PTR) { + toy_error(p, p->cur.loc, "cannot dereference non-pointer"); + return CFREE_CG_TYPE_NONE; + } + ty = cfree_cg_type_ptr_pointee(p->c, ty); + toy_ty = toy_type_pointee(p, toy_ty); + cfree_cg_indirect(p->cg); + cfree_cg_load(p->cg, toy_mem_access(p, ty)); + p->last_type = toy_ty != TOY_TYPE_NONE ? toy_ty : toy_type_from_cg(p, ty); + continue; + } + + if (toy_parser_match(p, TOK_LBRACKET)) { + CfreeCgTypeId idx_ty = toy_parse_expr(p); + if (idx_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after index"); + return CFREE_CG_TYPE_NONE; + } + if (idx_ty != p->int_type) { + toy_error(p, p->cur.loc, "index must be i64"); + return CFREE_CG_TYPE_NONE; + } + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR) { + CfreeCgTypeId pointee = cfree_cg_type_ptr_pointee(p->c, ty); + ToyTypeId source_pointee = toy_type_pointee(p, toy_ty); + if (cfree_cg_type_kind(p->c, pointee) == CFREE_CG_TYPE_ARRAY) { + CfreeCgLocal idx_slot = + cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_indirect(p->cg); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + ty = cfree_cg_type_array_elem(p->c, pointee); + toy_ty = toy_type_array_elem(p, source_pointee); + } else { + ty = pointee; + toy_ty = source_pointee; + } + } else if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { + ty = cfree_cg_type_array_elem(p->c, ty); + toy_ty = toy_type_array_elem(p, toy_ty); + } else { + toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_index(p->cg, 0); + cfree_cg_load(p->cg, toy_mem_access(p, ty)); + p->last_type = toy_ty != TOY_TYPE_NONE ? toy_ty : toy_type_from_cg(p, ty); + continue; + } + + if (toy_parser_match(p, TOK_DOT)) { + CfreeSym field_name; + uint32_t i, nfields; + int found = 0; + CfreeCgField found_field; + ToyNamedType* named; + ToyTypeId field_toy_type = TOY_TYPE_NONE; + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR && + cfree_cg_type_kind(p->c, cfree_cg_type_ptr_pointee(p->c, ty)) == + CFREE_CG_TYPE_RECORD) { + ty = cfree_cg_type_ptr_pointee(p->c, ty); + toy_ty = toy_type_pointee(p, toy_ty); + cfree_cg_indirect(p->cg); + } + named = toy_find_named_type_by_type(p, ty); + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + uint32_t field_index; + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD || + p->cur.int_value < 0 || + p->cur.int_value >= + (int64_t)cfree_cg_type_record_nfields(p->c, ty)) { + toy_error(p, p->cur.loc, "invalid tuple field"); + return CFREE_CG_TYPE_NONE; + } + field_index = (uint32_t)p->cur.int_value; + toy_parser_advance(p); + if (cfree_cg_type_record_field(p->c, ty, field_index, &found_field, + NULL) != 0) { + return CFREE_CG_TYPE_NONE; + } + cfree_cg_field(p->cg, field_index); + if (named && field_index < named->nfields) + field_toy_type = named->fields[field_index].toy_type; + ty = found_field.type; + cfree_cg_load(p->cg, toy_mem_access(p, ty)); + if (field_toy_type != TOY_TYPE_NONE) { + CfreeCgTypeId resolved = toy_type_resolved_cg(p, field_toy_type); + p->last_type = field_toy_type; + toy_ty = field_toy_type; + if (resolved != CFREE_CG_TYPE_NONE && resolved != ty) { + cfree_cg_bitcast(p->cg, resolved); + ty = resolved; + } + } else { + p->last_type = toy_type_from_cg(p, ty); + toy_ty = p->last_type; + } + continue; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected field name"); + return CFREE_CG_TYPE_NONE; + } + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD) { + toy_error(p, p->cur.loc, "field access on non-record"); + return CFREE_CG_TYPE_NONE; + } + nfields = cfree_cg_type_record_nfields(p->c, ty); + memset(&found_field, 0, sizeof found_field); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, ty, i, &field, NULL) == 0 && + field.name == field_name) { + found = 1; + found_field = field; + if (named && i < named->nfields) + field_toy_type = named->fields[i].toy_type; + cfree_cg_field(p->cg, i); + break; + } + } + if (!found) { + toy_error(p, p->cur.loc, "unknown record field"); + return CFREE_CG_TYPE_NONE; + } + ty = found_field.type; + cfree_cg_load(p->cg, toy_mem_access(p, ty)); + if (field_toy_type != TOY_TYPE_NONE) { + CfreeCgTypeId resolved = toy_type_resolved_cg(p, field_toy_type); + p->last_type = field_toy_type; + toy_ty = field_toy_type; + if (resolved != CFREE_CG_TYPE_NONE && resolved != ty) { + cfree_cg_bitcast(p->cg, resolved); + ty = resolved; + } + } else { + p->last_type = toy_type_from_cg(p, ty); + toy_ty = p->last_type; + } + continue; + } + + break; + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { + toy_set_loc(p); + if (toy_parser_match(p, TOK_MINUS)) { + CfreeCgTypeId ty = toy_parse_expr_unary(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_type_is_float(p, ty)) { + cfree_cg_fp_unop(p->cg, CFREE_CG_FP_NEG, 0); + return ty; + } + if (!toy_type_is_intlike(p, ty)) { + toy_error(p, p->cur.loc, "invalid operand for unary '-'"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_unop(p->cg, CFREE_CG_INT_NEG, 0); + return ty; + } + + if (toy_parser_match(p, TOK_BANG)) { + CfreeCgTypeId ty = toy_parse_expr_unary(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_intlike(p, ty)) { + toy_error(p, p->cur.loc, "invalid operand for '!'"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_unop(p->cg, CFREE_CG_INT_NOT, 0); + return ty; + } + + if (toy_parser_match(p, TOK_TILDE)) { + CfreeCgTypeId ty = toy_parse_expr_unary(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_type_is_intlike(p, ty)) { + toy_error(p, p->cur.loc, "invalid operand for '~'"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_unop(p->cg, CFREE_CG_INT_BNOT, 0); + return ty; + } + + if (toy_parser_match(p, TOK_AMPERSAND)) { + CfreeSym name; + CfreeCgTypeId ty; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier after '&'"); + return CFREE_CG_TYPE_NONE; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + + if (p->cur.kind == TOK_LBRACKET || p->cur.kind == TOK_DOTSTAR || + p->cur.kind == TOK_DOT) { + if (p->cur.kind == TOK_DOT) { + ToyVar* v = toy_find_var(p, name); + ToyGlobal* g = toy_find_global(p, name); + if (v && cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD) { + toy_push_var_lvalue(p, v); + ty = v->type; + } else if (g && + cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + ty = g->type; + } else { + ty = toy_push_named_rvalue(p, name); + } + } else { + ty = toy_push_named_rvalue(p, name); + } + if (ty == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "undefined variable"); + return CFREE_CG_TYPE_NONE; + } + for (;;) { + if (toy_parser_match(p, TOK_LBRACKET)) { + CfreeCgTypeId idx_ty = toy_parse_expr(p); + if (idx_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after index"); + return CFREE_CG_TYPE_NONE; + } + if (idx_ty != p->int_type) { + toy_error(p, p->cur.loc, "index must be i64"); + return CFREE_CG_TYPE_NONE; + } + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR) { + CfreeCgTypeId pointee = cfree_cg_type_ptr_pointee(p->c, ty); + if (cfree_cg_type_kind(p->c, pointee) == CFREE_CG_TYPE_ARRAY) { + CfreeCgLocal idx_slot = + cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_indirect(p->cg); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + ty = cfree_cg_type_array_elem(p->c, pointee); + } else { + ty = pointee; + } + } else if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { + ty = cfree_cg_type_array_elem(p->c, ty); + } else { + toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_index(p->cg, 0); + continue; + } + if (toy_parser_match(p, TOK_DOTSTAR)) { + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_PTR) { + toy_error(p, p->cur.loc, "cannot dereference non-pointer"); + return CFREE_CG_TYPE_NONE; + } + ty = cfree_cg_type_ptr_pointee(p->c, ty); + cfree_cg_indirect(p->cg); + continue; + } + if (toy_parser_match(p, TOK_DOT)) { + CfreeCgField field; + uint32_t field_index = 0; + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR && + cfree_cg_type_kind(p->c, + cfree_cg_type_ptr_pointee(p->c, ty)) == + CFREE_CG_TYPE_RECORD) { + ty = cfree_cg_type_ptr_pointee(p->c, ty); + cfree_cg_indirect(p->cg); + } + if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD) { + toy_error(p, p->cur.loc, "field access on non-record"); + return CFREE_CG_TYPE_NONE; + } + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (p->cur.int_value < 0 || + p->cur.int_value >= + (int64_t)cfree_cg_type_record_nfields(p->c, ty)) { + toy_error(p, p->cur.loc, "invalid tuple field"); + return CFREE_CG_TYPE_NONE; + } + field_index = (uint32_t)p->cur.int_value; + toy_parser_advance(p); + if (cfree_cg_type_record_field(p->c, ty, field_index, &field, + NULL) != 0) + return CFREE_CG_TYPE_NONE; + } else { + CfreeSym field_name; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected field name"); + return CFREE_CG_TYPE_NONE; + } + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_record_field_index(p, ty, field_name, &field_index, + &field)) { + toy_error(p, p->cur.loc, "unknown record field"); + return CFREE_CG_TYPE_NONE; + } + } + cfree_cg_field(p->cg, field_index); + ty = field.type; + continue; + } + break; + } + cfree_cg_addr(p->cg); + return cfree_cg_type_ptr(p->c, ty, 0); + } + + { + ToyVar* v = toy_find_var(p, name); + if (v) { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, v->type, 0); + toy_push_var_addr(p, v); + p->last_type = toy_type_register_ptr(p, ptr_ty, v->toy_type, 0); + return ptr_ty; + } else { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, g->type, 0); + cfree_cg_push_symbol_addr(p->cg, g->sym, 0); + p->last_type = toy_type_register_ptr(p, ptr_ty, g->toy_type, 0); + return ptr_ty; + } else { + ToyFn* fn = toy_find_fn(p, name); + if (fn) { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, fn->type, 0); + cfree_cg_push_symbol_addr(p->cg, fn->sym, 0); + p->last_type = toy_type_register_ptr(p, ptr_ty, fn->toy_type, 0); + return ptr_ty; + } + } + toy_error(p, p->cur.loc, "undefined variable"); + return CFREE_CG_TYPE_NONE; + } + } + } + + return toy_parse_expr_postfix(p); +} + +static CfreeCgTypeId toy_parse_expr_cast(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_unary(p); + while (ty != CFREE_CG_TYPE_NONE && toy_parser_match(p, TOK_AS)) { + CfreeCgTypeId dst = toy_parse_type(p); + if (dst == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_emit_cast(p, ty, dst)) return CFREE_CG_TYPE_NONE; + ty = dst; + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_mul(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_cast(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_STAR || p->cur.kind == TOK_SLASH || + p->cur.kind == TOK_PERCENT) { + ToyTokenKind op = p->cur.kind; + CfreeCgIntBinOp binop; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_ARITH)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_cast(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { + toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); + return CFREE_CG_TYPE_NONE; + } + if (toy_type_is_float(p, ty)) { + CfreeCgFpBinOp fp_op; + if (op == TOK_PERCENT) { + toy_error(p, p->cur.loc, "floating-point remainder is unsupported"); + return CFREE_CG_TYPE_NONE; + } + switch (op) { + case TOK_STAR: + fp_op = CFREE_CG_FP_MUL; + break; + case TOK_SLASH: + fp_op = CFREE_CG_FP_DIV; + break; + case TOK_PERCENT: + fp_op = CFREE_CG_FP_REM; + break; + default: + return CFREE_CG_TYPE_NONE; + } + cfree_cg_fp_binop(p->cg, fp_op, 0); + } else { + switch (op) { + case TOK_STAR: + binop = CFREE_CG_INT_MUL; + break; + case TOK_SLASH: + binop = CFREE_CG_INT_SDIV; + break; + case TOK_PERCENT: + binop = CFREE_CG_INT_SREM; + break; + default: + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_binop(p->cg, binop, 0); + } + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_add(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_mul(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_PLUS || p->cur.kind == TOK_MINUS) { + ToyTokenKind op = p->cur.kind; + CfreeCgIntBinOp binop = + (op == TOK_PLUS) ? CFREE_CG_INT_ADD : CFREE_CG_INT_SUB; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_ARITH)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_mul(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { + toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); + return CFREE_CG_TYPE_NONE; + } + if (toy_type_is_float(p, ty)) { + cfree_cg_fp_binop(p->cg, + op == TOK_PLUS ? CFREE_CG_FP_ADD : CFREE_CG_FP_SUB, + 0); + } else { + cfree_cg_int_binop(p->cg, binop, 0); + } + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_cmp(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_add(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (p->cur.kind == TOK_EQEQ || p->cur.kind == TOK_NE || + p->cur.kind == TOK_LT || p->cur.kind == TOK_GT || + p->cur.kind == TOK_LE || p->cur.kind == TOK_GE) { + ToyTokenKind op = p->cur.kind; + CfreeCgIntCmpOp cmp; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_CMP)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_add(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != ty2) { + toy_error(p, p->cur.loc, "comparison operands must have same type"); + return CFREE_CG_TYPE_NONE; + } + if (toy_type_is_float(p, ty)) { + CfreeCgFpCmpOp fp_cmp; + switch (op) { + case TOK_EQEQ: + fp_cmp = CFREE_CG_FP_OEQ; + break; + case TOK_NE: + fp_cmp = CFREE_CG_FP_ONE; + break; + case TOK_LT: + fp_cmp = CFREE_CG_FP_OLT; + break; + case TOK_GT: + fp_cmp = CFREE_CG_FP_OGT; + break; + case TOK_LE: + fp_cmp = CFREE_CG_FP_OLE; + break; + case TOK_GE: + fp_cmp = CFREE_CG_FP_OGE; + break; + default: + return CFREE_CG_TYPE_NONE; + } + cfree_cg_fp_cmp(p->cg, fp_cmp); + } else if (toy_type_is_intlike(p, ty) || toy_type_is_ptr(p, ty)) { + switch (op) { + case TOK_EQEQ: + cmp = CFREE_CG_INT_EQ; + break; + case TOK_NE: + cmp = CFREE_CG_INT_NE; + break; + case TOK_LT: + cmp = CFREE_CG_INT_LT_S; + break; + case TOK_GT: + cmp = CFREE_CG_INT_GT_S; + break; + case TOK_LE: + cmp = CFREE_CG_INT_LE_S; + break; + case TOK_GE: + cmp = CFREE_CG_INT_GE_S; + break; + default: + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_cmp(p->cg, cmp); + } else { + toy_error(p, p->cur.loc, "comparison operands must be scalar"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_zext(p->cg, p->int_type); + ty = p->int_type; + toy_note_cg_result_type(p, ty); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_shift(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_cmp(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_SHL || p->cur.kind == TOK_SHR) { + ToyTokenKind op = p->cur.kind; + CfreeCgIntBinOp binop = + (op == TOK_SHL) ? CFREE_CG_INT_SHL : CFREE_CG_INT_ASHR; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_SHIFT)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_cmp(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "shift operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_binop(p->cg, binop, 0); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_band(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_shift(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_AMPERSAND) { + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_AND)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_shift(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_bxor(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_band(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_CARET) { + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_XOR)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_band(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_binop(p->cg, CFREE_CG_INT_XOR, 0); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_bor(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_bxor(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_PIPE) { + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_BIT_OR)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + CfreeCgTypeId ty2 = toy_parse_expr_bxor(p); + if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type || ty2 != p->int_type) { + toy_error(p, p->cur.loc, "bitwise operands must be int"); + return CFREE_CG_TYPE_NONE; + } + cfree_cg_int_binop(p->cg, CFREE_CG_INT_OR, 0); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_bor(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_AND) { + CfreeCgTypeId bool_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + CfreeCgLabel false_label; + CfreeCgLabel end_label; + CfreeCgLocal result_slot; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_LOGIC_AND)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + false_label = cfree_cg_label_new(p->cg); + end_label = cfree_cg_label_new(p->cg); + result_slot = cfree_cg_local(p->cg, bool_ty, toy_slot_attrs(0)); + if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_false(p->cg, false_label); + ty = toy_parse_expr_bor(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_false(p->cg, false_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 1, bool_ty); + cfree_cg_store(p->cg, toy_mem_access(p, bool_ty)); + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, false_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 0, bool_ty); + cfree_cg_store(p->cg, toy_mem_access(p, bool_ty)); + cfree_cg_label_place(p->cg, end_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_load(p->cg, toy_mem_access(p, bool_ty)); + ty = bool_ty; + toy_note_cg_result_type(p, ty); + } + return ty; +} + +static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr_and(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + while (p->cur.kind == TOK_OR) { + CfreeCgTypeId bool_ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + CfreeCgLabel true_label; + CfreeCgLabel end_label; + CfreeCgLocal result_slot; + if (!toy_note_expr_island(p, TOY_EXPR_ISLAND_LOGIC_OR)) + return CFREE_CG_TYPE_NONE; + toy_parser_advance(p); + true_label = cfree_cg_label_new(p->cg); + end_label = cfree_cg_label_new(p->cg); + result_slot = cfree_cg_local(p->cg, bool_ty, toy_slot_attrs(0)); + if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_true(p->cg, true_label); + ty = toy_parse_expr_and(p); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; + cfree_cg_branch_true(p->cg, true_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 0, bool_ty); + cfree_cg_store(p->cg, toy_mem_access(p, bool_ty)); + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, true_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_push_int(p->cg, 1, bool_ty); + cfree_cg_store(p->cg, toy_mem_access(p, bool_ty)); + cfree_cg_label_place(p->cg, end_label); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_load(p->cg, toy_mem_access(p, bool_ty)); + ty = bool_ty; + toy_note_cg_result_type(p, ty); + } + return ty; +} + +CfreeCgTypeId toy_parse_expr(ToyParser* p) { + uint32_t saved_island_mask = p->expr_island_mask; + CfreeCgTypeId ty; + p->expr_island_mask = 0; + ty = toy_parse_expr_or(p); + if (ty != CFREE_CG_TYPE_NONE && + (p->cur.kind == TOK_ANDAND || p->cur.kind == TOK_PIPEPIPE)) { + toy_error(p, p->cur.loc, + "legacy logical operator is unsupported; use 'and' or 'or'"); + ty = CFREE_CG_TYPE_NONE; + } + p->expr_island_mask = saved_island_mask; + return ty; +} diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -0,0 +1,378 @@ +#ifndef CFREE_TOY_INTERNAL_H +#define CFREE_TOY_INTERNAL_H + +#include "lexer.h" + +#include <cfree/cg.h> +#include <cfree/frontend.h> +#include <stddef.h> +#include <stdint.h> + +typedef uint32_t ToyTypeId; + +#define TOY_TYPE_NONE ((ToyTypeId)0) + +typedef struct ToyVar { + CfreeSym name; + CfreeCgTypeId type; + ToyTypeId toy_type; + CfreeCgLocal local; + CfreeCgSym static_sym; + int is_static; + int mutable; +} ToyVar; + +typedef struct ToyFn { + CfreeSym name; + CfreeCgSym sym; + CfreeCgTypeId type; + ToyTypeId toy_type; + CfreeCgTypeId ret; + ToyTypeId toy_ret; + CfreeCgTypeId* params; + ToyTypeId* toy_params; + size_t nparams; + int variadic; +} ToyFn; + +typedef struct ToyGlobal { + CfreeSym name; + CfreeCgSym sym; + CfreeCgTypeId type; + ToyTypeId toy_type; + int mutable; +} ToyGlobal; + +typedef struct ToyAttrSet { + CfreeCgSymbolAttrs sym; + CfreeCgFuncAttrs func; + CfreeCgObjectAttrs object; + CfreeCgDataDefAttrs data; + CfreeCgCallConv call_conv; + uint32_t attr_kinds; + int has_call_conv; + int has_static; + int is_common; +} ToyAttrSet; + +enum { + TOY_ATTR_SYMBOL = 1u << 0, + TOY_ATTR_FUNC = 1u << 1, + TOY_ATTR_OBJECT = 1u << 2, + TOY_ATTR_DATA = 1u << 3, + TOY_ATTR_SECTION = 1u << 4, + TOY_ATTR_OBJECT_DATA = 1u << 5, +}; + +typedef enum ToyNamedTypeKind { + TOY_NAMED_ALIAS, + TOY_NAMED_RECORD, + TOY_NAMED_TUPLE, + TOY_NAMED_ENUM, +} ToyNamedTypeKind; + +typedef struct ToyEnumConst { + CfreeSym name; + int64_t value; +} ToyEnumConst; + +typedef struct ToyRecordFieldInfo { + CfreeSym name; + CfreeCgTypeId storage_type; + ToyTypeId toy_type; +} ToyRecordFieldInfo; + +typedef enum ToyTypeKind { + TOY_TYPE_BUILTIN, + TOY_TYPE_ALIAS, + TOY_TYPE_NOMINAL_RECORD, + TOY_TYPE_TUPLE_RECORD, + TOY_TYPE_ENUM, + TOY_TYPE_ARRAY, + TOY_TYPE_PTR, + TOY_TYPE_FUNC, + TOY_TYPE_QUALIFIED, + TOY_TYPE_ANON_RECORD, +} ToyTypeKind; + +typedef enum ToyTypeQual { + TOY_TYPE_QUAL_CONST = 1u << 0, + TOY_TYPE_QUAL_VOLATILE = 1u << 1, + TOY_TYPE_QUAL_RESTRICT = 1u << 2, +} ToyTypeQual; + +typedef struct ToyType { + ToyTypeKind kind; + CfreeCgTypeId cg; + CfreeSym name; + ToyTypeId base; + ToyTypeId elem; + ToyTypeId pointee; + ToyTypeId ret; + ToyTypeId* params; + size_t nparams; + uint64_t count; + uint32_t address_space; + uint32_t quals; + int variadic; +} ToyType; + +typedef struct ToyNamedType { + CfreeSym name; + CfreeCgTypeId type; + ToyTypeId toy_type; + ToyNamedTypeKind kind; + CfreeCgTypeId base_type; + ToyEnumConst* enum_values; + size_t nenum_values; + size_t cap_enum_values; + ToyRecordFieldInfo* fields; + size_t nfields; + size_t cap_fields; +} ToyNamedType; + +typedef struct ToyTypeTable { + ToyType* types; + size_t ntypes; + size_t cap_types; + ToyNamedType* named; + size_t count; + size_t cap; +} ToyTypeTable; + +typedef enum ToyScopeKind { + TOY_SCOPE_LOOP, + TOY_SCOPE_SWITCH, +} ToyScopeKind; + +typedef struct ToyScope { + CfreeSym name; + ToyScopeKind kind; + CfreeCgScope cg_scope; + CfreeCgTypeId result_type; + ToyTypeId result_toy_type; +} ToyScope; + +typedef struct ToyLabel { + CfreeSym name; + CfreeCgLabel label; +} ToyLabel; + +typedef enum ToyExprIsland { + TOY_EXPR_ISLAND_ARITH = 1u << 0, + TOY_EXPR_ISLAND_SHIFT = 1u << 1, + TOY_EXPR_ISLAND_CMP = 1u << 2, + TOY_EXPR_ISLAND_BIT_AND = 1u << 3, + TOY_EXPR_ISLAND_BIT_XOR = 1u << 4, + TOY_EXPR_ISLAND_BIT_OR = 1u << 5, + TOY_EXPR_ISLAND_LOGIC_AND = 1u << 6, + TOY_EXPR_ISLAND_LOGIC_OR = 1u << 7, +} ToyExprIsland; + +typedef struct ToyParser { + ToyLexer lex; + ToyToken cur; + CfreeCompiler* c; + CfreeCg* cg; + CfreeCgBuiltinTypes types; + CfreeCgTypeId int_type; + CfreeCgTypeId int_ptr_type; + CfreeCgTypeId va_list_type; + CfreeTarget target; + + ToyVar* vars; + size_t nvars; + size_t cap_vars; + + ToyFn* fns; + size_t nfns; + size_t cap_fns; + + ToyGlobal* globals; + size_t nglobals; + size_t cap_globals; + + ToyTypeTable type_table; + + ToyScope* scopes; + size_t nscopes; + size_t cap_scopes; + + ToyLabel* labels; + size_t nlabels; + size_t cap_labels; + + CfreeCgLabel* goto_targets; + size_t cap_goto_targets; + + CfreeCgTypeId cur_fn_ret; + ToyTypeId cur_fn_ret_toy; + CfreeDiagSink* diag; + int has_error; + uint32_t static_counter; + uint32_t expr_island_mask; + ToyTypeId last_type; +} ToyParser; + +CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty); +void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, + const uint8_t* data, size_t len); +void toy_parser_dispose(ToyParser* p); +int toy_parser_reserve(ToyParser* p, void** items, size_t* cap, size_t want, + size_t elem_size, const char* what); +void toy_parser_advance(ToyParser* p); +int toy_parser_match(ToyParser* p, ToyTokenKind kind); +int toy_parser_expect(ToyParser* p, ToyTokenKind kind); +void toy_error(ToyParser* p, CfreeSrcLoc loc, const char* fmt, ...); +void toy_set_loc(ToyParser* p); +CfreeSym toy_tok_sym(ToyParser* p, ToyToken tok); +int toy_sym_is(ToyParser* p, CfreeSym sym, const char* name); +int toy_expect_comma(ToyParser* p); +int toy_skip_attr_list_ex(ToyParser* p, int* has_static); +int toy_skip_attr_list(ToyParser* p); +void toy_emit_int_bytes(ToyParser* p, uint64_t v, uint8_t* buf, size_t n); +int toy_parse_number_arg(ToyParser* p, int64_t* out); +int toy_parse_string_sym(ToyParser* p, CfreeSym* out, size_t* len_out); +int toy_parse_string_bytes(ToyParser* p, uint8_t* out, size_t cap, + size_t* len_out); +int toy_parse_attr_dot_name(ToyParser* p, CfreeSym* out); +int toy_parse_attr_int_arg(ToyParser* p, int64_t* out); +int toy_parse_callconv_const(ToyParser* p, CfreeCgCallConv* out); +int toy_parse_symbol_feature_const(ToyParser* p, CfreeCgSymbolFeature* out); +int toy_parse_backend_feature_const(ToyParser* p, uint64_t* out); +int toy_parse_rounding_const(ToyParser* p, CfreeCgRounding* out); +int toy_parse_attr_list(ToyParser* p, ToyAttrSet* attrs, + CfreeSymBind default_bind); +int toy_validate_attr_placement(ToyParser* p, const ToyAttrSet* attrs, + uint32_t allowed, const char* message); +int toy_parse_abi_attr_list(ToyParser* p, CfreeCgAbiAttrs* attrs); +int toy_parse_field_attr_list(ToyParser* p, CfreeCgField* field); +int toy_parse_record_attr_list(ToyParser* p, int* packed_out, + uint32_t* align_out); +CfreeCgTypeId toy_parse_type(ToyParser* p); +CfreeCgTypeId toy_parse_expr(ToyParser* p); +CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name); +int toy_parse_call_args(ToyParser* p, ToyToken call_tok, CfreeCgTypeId fn_ty, + const ToyTypeId* toy_params, size_t toy_nparams, + size_t* out_nargs); +int toy_emit_truthy(ToyParser* p, CfreeCgTypeId type); +int toy_parse_switch_label_value(ToyParser* p, CfreeCgTypeId selector_ty, + int64_t* out); +CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgTypeId toy_parse_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgTypeId toy_parse_memory_builtin_call(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgTypeId toy_parse_atomic_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgTypeId toy_parse_low_level_builtin_call(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgTypeId toy_parse_low_level_generic_builtin(ToyParser* p, CfreeSym name, + int* recognized); +CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type); +CfreeCgLocalAttrs toy_slot_attrs(CfreeSym name); +CfreeSym toy_c_linkage_name(ToyParser* p, CfreeSym source_name); +void toy_inline_asm(ToyParser* p, CfreeSym tmpl, + const CfreeCgAsmOperand* outputs, uint32_t noutputs, + const CfreeCgAsmOperand* inputs, uint32_t ninputs, + const CfreeSym* clobbers, uint32_t nclobbers, + uint32_t flags, uint32_t clobber_abi_sets); +int toy_parse_arch_string(ToyParser* p, CfreeSym* out, size_t* len_out); +int toy_parse_typed_asm_tail(ToyParser* p, CfreeCgTypeId result_ty, + CfreeSym tmpl, size_t tmpl_len); +int toy_parse_data_array_builtin(ToyParser* p, CfreeCgTypeId elem_ty, + uint64_t total_size, uint64_t* pos); +int toy_parse_global_record_initializer(ToyParser* p, CfreeCgSym sym, + CfreeCgTypeId record_ty, + CfreeCgDataDefAttrs data_attrs); +int toy_parse_global_var(ToyParser* p, int is_extern, int is_pub); +int toy_parse_type_alias_decl(ToyParser* p); +int toy_parse_record_decl(ToyParser* p); +int toy_parse_tuple_decl(ToyParser* p); +int toy_parse_enum_decl(ToyParser* p); +int toy_parse_alias_decl(ToyParser* p, int is_pub); +int toy_parse_fn(ToyParser* p, int is_extern, int is_pub); +int toy_parse_block(ToyParser* p); +CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name); +int toy_type_is_intlike(ToyParser* p, CfreeCgTypeId ty); +int toy_type_is_float(ToyParser* p, CfreeCgTypeId ty); +int toy_type_is_ptr(ToyParser* p, CfreeCgTypeId ty); +uint32_t toy_type_int_width(ToyParser* p, CfreeCgTypeId ty); +int toy_validate_bit_range(ToyParser* p, CfreeCgTypeId ty, int64_t lo, + int64_t width, int allow_full_width); +int toy_target_code(ToyParser* p); +int toy_parse_va_list_addr_arg(ToyParser* p); +CfreeCgTypeId toy_ptr_pointee_func_type(ToyParser* p, CfreeCgTypeId ptr_ty); +void toy_type_register_builtins(ToyParser* p); +const ToyType* toy_type_get(ToyParser* p, ToyTypeId id); +CfreeCgTypeId toy_type_cg(ToyParser* p, ToyTypeId id); +ToyTypeId toy_type_from_cg(ToyParser* p, CfreeCgTypeId cg); +ToyTypeId toy_type_register_alias(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, CfreeCgTypeId base); +ToyTypeId toy_type_register_named_record(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, int is_tuple); +ToyTypeId toy_type_register_enum(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, CfreeCgTypeId base); +ToyTypeId toy_type_register_qualified(ToyParser* p, CfreeCgTypeId cg, + CfreeCgTypeId base, uint32_t quals); +ToyTypeId toy_type_register_ptr(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId pointee, uint32_t address_space); +ToyTypeId toy_type_register_func(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId ret, const ToyTypeId* params, + size_t nparams, int variadic); +int toy_type_accepts_type(ToyParser* p, ToyTypeId expected, ToyTypeId actual); +CfreeCgTypeId toy_type_resolved_cg(ToyParser* p, ToyTypeId id); +ToyTypeId toy_type_pointee(ToyParser* p, ToyTypeId id); +ToyTypeId toy_type_array_elem(ToyParser* p, ToyTypeId id); +int toy_record_field_index(ToyParser* p, CfreeCgTypeId record_ty, + CfreeSym field_name, uint32_t* index_out, + CfreeCgField* field_out); +int toy_add_named_type(ToyParser* p, CfreeSym name, CfreeCgTypeId type, + ToyNamedTypeKind kind, CfreeCgTypeId base_type); +int toy_set_named_type_fields(ToyParser* p, ToyNamedType* named, + const ToyRecordFieldInfo* fields, + size_t nfields); +int toy_set_named_type_enum_values(ToyParser* p, ToyNamedType* named, + const ToyEnumConst* values, + size_t nvalues); + +ToyVar* toy_find_var(ToyParser* p, CfreeSym name); +int toy_add_local(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + CfreeCgLocal slot, int mutable); +int toy_add_local_typed(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + ToyTypeId toy_type, CfreeCgLocal slot, int mutable); +int toy_add_static_local(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + CfreeCgSym sym, int mutable); +int toy_add_static_local_typed(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + ToyTypeId toy_type, CfreeCgSym sym, + int mutable); +ToyFn* toy_find_fn(ToyParser* p, CfreeSym name); +ToyFn* toy_add_fn(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, CfreeCgTypeId ret, + const CfreeCgTypeId* params, size_t nparams, int variadic); +ToyFn* toy_add_fn_typed(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, ToyTypeId toy_type, + CfreeCgTypeId ret, ToyTypeId toy_ret, + const CfreeCgTypeId* params, + const ToyTypeId* toy_params, size_t nparams, + int variadic); +ToyGlobal* toy_find_global(ToyParser* p, CfreeSym name); +int toy_add_global(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, int mutable); +int toy_add_global_typed(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, ToyTypeId toy_type, + int mutable); +ToyNamedType* toy_find_named_type(ToyParser* p, CfreeSym name); +ToyNamedType* toy_find_named_type_by_type(ToyParser* p, CfreeCgTypeId type); +ToyLabel* toy_find_label(ToyParser* p, CfreeSym name); +ToyLabel* toy_declare_label(ToyParser* p, CfreeSym name); +ToyScope* toy_find_scope(ToyParser* p, CfreeSym name); +ToyScope* toy_find_innermost_loop_scope(ToyParser* p); +void toy_push_var_lvalue(ToyParser* p, const ToyVar* v); +void toy_push_var_addr(ToyParser* p, const ToyVar* v); +CfreeCgSym toy_find_decl_sym(ToyParser* p, CfreeSym name); + +int toy_parse_program(ToyParser* p); + +#endif diff --git a/lang/toy/lexer.c b/lang/toy/lexer.c @@ -0,0 +1,293 @@ +#include "lexer.h" + +#include <stdlib.h> +#include <string.h> + +void toy_lexer_init(ToyLexer* lex, const uint8_t* data, size_t len) { + lex->cur = data; + lex->end = data + len; + lex->bol = data; + lex->line = 1; +} + +static int toy_is_space(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || + c == '\v'; +} + +static int toy_is_digit(uint8_t c) { return c >= '0' && c <= '9'; } + +static int toy_is_alpha(uint8_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; +} + +static int toy_is_alnum(uint8_t c) { + return toy_is_alpha(c) || toy_is_digit(c); +} + +static void toy_lexer_advance_line(ToyLexer* lex) { + lex->bol = lex->cur + 1; + lex->line++; +} + +static void toy_skip_ws(ToyLexer* lex) { + while (lex->cur < lex->end && toy_is_space(*lex->cur)) { + if (*lex->cur == '\n') toy_lexer_advance_line(lex); + lex->cur++; + } +} + +static ToyToken toy_lexer_emit(ToyLexer* lex, ToyTokenKind kind, + const uint8_t* start) { + ToyToken tok; + tok.kind = kind; + tok.loc.file_id = 0; + tok.loc.line = lex->line; + tok.loc.col = (uint32_t)(start - lex->bol) + 1; + tok.text = start; + tok.text_len = (size_t)(lex->cur - start); + tok.int_value = 0; + tok.float_value = 0.0; + tok.is_float = 0; + return tok; +} + +ToyToken toy_lexer_next(ToyLexer* lex) { + const uint8_t* start; + ToyToken tok; + + toy_skip_ws(lex); + start = lex->cur; + if (lex->cur >= lex->end) { + tok.kind = TOK_EOF; + tok.loc.file_id = 0; + tok.loc.line = lex->line; + tok.loc.col = (uint32_t)(start - lex->bol) + 1; + tok.text = start; + tok.text_len = 0; + tok.int_value = 0; + tok.float_value = 0.0; + tok.is_float = 0; + return tok; + } + + uint8_t c = *lex->cur++; + + switch (c) { + case '(': + return toy_lexer_emit(lex, TOK_LPAREN, start); + case ')': + return toy_lexer_emit(lex, TOK_RPAREN, start); + case '{': + return toy_lexer_emit(lex, TOK_LBRACE, start); + case '}': + return toy_lexer_emit(lex, TOK_RBRACE, start); + case '[': + return toy_lexer_emit(lex, TOK_LBRACKET, start); + case ']': + return toy_lexer_emit(lex, TOK_RBRACKET, start); + case ',': + return toy_lexer_emit(lex, TOK_COMMA, start); + case ';': + return toy_lexer_emit(lex, TOK_SEMI, start); + case ':': + return toy_lexer_emit(lex, TOK_COLON, start); + case '+': + return toy_lexer_emit(lex, TOK_PLUS, start); + case '*': + return toy_lexer_emit(lex, TOK_STAR, start); + case '/': + return toy_lexer_emit(lex, TOK_SLASH, start); + case '%': + return toy_lexer_emit(lex, TOK_PERCENT, start); + case '&': + if (lex->cur < lex->end && *lex->cur == '&') { + lex->cur++; + return toy_lexer_emit(lex, TOK_ANDAND, start); + } + return toy_lexer_emit(lex, TOK_AMPERSAND, start); + case '|': + if (lex->cur < lex->end && *lex->cur == '|') { + lex->cur++; + return toy_lexer_emit(lex, TOK_PIPEPIPE, start); + } + return toy_lexer_emit(lex, TOK_PIPE, start); + case '^': + return toy_lexer_emit(lex, TOK_CARET, start); + case '~': + return toy_lexer_emit(lex, TOK_TILDE, start); + case '=': + if (lex->cur < lex->end && *lex->cur == '=') { + lex->cur++; + return toy_lexer_emit(lex, TOK_EQEQ, start); + } + return toy_lexer_emit(lex, TOK_EQ, start); + case '!': + if (lex->cur < lex->end && *lex->cur == '=') { + lex->cur++; + return toy_lexer_emit(lex, TOK_NE, start); + } + return toy_lexer_emit(lex, TOK_BANG, start); + case '<': + if (lex->cur < lex->end && *lex->cur == '<') { + lex->cur++; + return toy_lexer_emit(lex, TOK_SHL, start); + } + if (lex->cur < lex->end && *lex->cur == '=') { + lex->cur++; + return toy_lexer_emit(lex, TOK_LE, start); + } + return toy_lexer_emit(lex, TOK_LT, start); + case '>': + if (lex->cur < lex->end && *lex->cur == '>') { + lex->cur++; + return toy_lexer_emit(lex, TOK_SHR, start); + } + if (lex->cur < lex->end && *lex->cur == '=') { + lex->cur++; + return toy_lexer_emit(lex, TOK_GE, start); + } + return toy_lexer_emit(lex, TOK_GT, start); + case '-': + return toy_lexer_emit(lex, TOK_MINUS, start); + case '.': + if (lex->cur + 1 < lex->end && lex->cur[0] == '.' && + lex->cur[1] == '.') { + lex->cur += 2; + return toy_lexer_emit(lex, TOK_DOTDOTDOT, start); + } + if (lex->cur < lex->end && *lex->cur == '*') { + lex->cur++; + return toy_lexer_emit(lex, TOK_DOTSTAR, start); + } + return toy_lexer_emit(lex, TOK_DOT, start); + case '@': + return toy_lexer_emit(lex, TOK_AT, start); + } + + if (toy_is_digit(c)) { + int64_t v = (int64_t)(c - '0'); + int is_float = 0; + while (lex->cur < lex->end && toy_is_digit(*lex->cur)) { + v = v * 10 + (int64_t)(*lex->cur - '0'); + lex->cur++; + } + if (lex->cur < lex->end && *lex->cur == '.' && + lex->cur + 1 < lex->end && toy_is_digit(lex->cur[1])) { + is_float = 1; + lex->cur++; + while (lex->cur < lex->end && toy_is_digit(*lex->cur)) lex->cur++; + } + tok = toy_lexer_emit(lex, TOK_NUMBER, start); + tok.int_value = v; + tok.is_float = is_float; + if (is_float) { + char buf[64]; + size_t len = tok.text_len; + if (len >= sizeof buf) len = sizeof buf - 1; + memcpy(buf, tok.text, len); + buf[len] = '\0'; + tok.float_value = strtod(buf, NULL); + } + return tok; + } + + if (toy_is_alpha(c)) { + while (lex->cur < lex->end && toy_is_alnum(*lex->cur)) lex->cur++; + size_t len = (size_t)(lex->cur - start); + ToyTokenKind kind = TOK_IDENT; + if (len == 2 && start[0] == 'f' && start[1] == 'n') + kind = TOK_FN; + else if (len == 2 && start[0] == 'a' && start[1] == 's') + kind = TOK_AS; + else if (len == 3 && start[0] == 'a' && start[1] == 'n' && start[2] == 'd') + kind = TOK_AND; + else if (len == 2 && start[0] == 'o' && start[1] == 'r') + kind = TOK_OR; + else if (len == 3 && start[0] == 'v' && start[1] == 'a' && start[2] == 'r') + kind = TOK_VAR; + else if (len == 3 && start[0] == 'i' && start[1] == 'n' && start[2] == 't') + kind = TOK_INT; + else if (len == 3 && start[0] == 'l' && start[1] == 'e' && start[2] == 't') + kind = TOK_LET; + else if (len == 2 && start[0] == 'i' && start[1] == 'f') + kind = TOK_IF; + else if (len == 4 && start[0] == 'e' && start[1] == 'l' && + start[2] == 's' && start[3] == 'e') + kind = TOK_ELSE; + else if (len == 5 && start[0] == 'w' && start[1] == 'h' && + start[2] == 'i' && start[3] == 'l' && start[4] == 'e') + kind = TOK_WHILE; + else if (len == 6 && start[0] == 's' && start[1] == 'w' && + start[2] == 'i' && start[3] == 't' && start[4] == 'c' && + start[5] == 'h') + kind = TOK_SWITCH; + else if (len == 7 && start[0] == 'd' && start[1] == 'e' && + start[2] == 'f' && start[3] == 'a' && start[4] == 'u' && + start[5] == 'l' && start[6] == 't') + kind = TOK_DEFAULT; + else if (len == 5 && start[0] == 'l' && start[1] == 'a' && + start[2] == 'b' && start[3] == 'e' && start[4] == 'l') + kind = TOK_LABEL; + else if (len == 4 && start[0] == 'g' && start[1] == 'o' && + start[2] == 't' && start[3] == 'o') + kind = TOK_GOTO; + else if (len == 6 && start[0] == 'w' && start[1] == 'i' && + start[2] == 't' && start[3] == 'h' && start[4] == 'i' && + start[5] == 'n') + kind = TOK_WITHIN; + else if (len == 5 && start[0] == 'b' && start[1] == 'r' && + start[2] == 'e' && start[3] == 'a' && start[4] == 'k') + kind = TOK_BREAK; + else if (len == 8 && start[0] == 'c' && start[1] == 'o' && + start[2] == 'n' && start[3] == 't' && start[4] == 'i' && + start[5] == 'n' && start[6] == 'u' && start[7] == 'e') + kind = TOK_CONTINUE; + else if (len == 6 && start[0] == 'r' && start[1] == 'e' && + start[2] == 't' && start[3] == 'u' && start[4] == 'r' && + start[5] == 'n') + kind = TOK_RETURN; + else if (len == 4 && start[0] == 't' && start[1] == 'a' && + start[2] == 'i' && start[3] == 'l') + kind = TOK_TAIL; + else if (len == 4 && start[0] == 't' && start[1] == 'y' && + start[2] == 'p' && start[3] == 'e') + kind = TOK_TYPE; + else if (len == 3 && start[0] == 'p' && start[1] == 'u' && start[2] == 'b') + kind = TOK_PUB; + else if (len == 6 && start[0] == 'e' && start[1] == 'x' && + start[2] == 't' && start[3] == 'e' && start[4] == 'r' && + start[5] == 'n') + kind = TOK_EXTERN; + else if (len == 5 && start[0] == 'a' && start[1] == 'l' && + start[2] == 'i' && start[3] == 'a' && start[4] == 's') + kind = TOK_ALIAS; + else if (len == 6 && start[0] == 'r' && start[1] == 'e' && + start[2] == 'c' && start[3] == 'o' && start[4] == 'r' && + start[5] == 'd') + kind = TOK_RECORD; + else if (len == 5 && start[0] == 't' && start[1] == 'u' && + start[2] == 'p' && start[3] == 'l' && start[4] == 'e') + kind = TOK_TUPLE; + else if (len == 4 && start[0] == 'e' && start[1] == 'n' && + start[2] == 'u' && start[3] == 'm') + kind = TOK_ENUM; + return toy_lexer_emit(lex, kind, start); + } + + if (c == '"') { + while (lex->cur < lex->end && *lex->cur != '"') { + if (*lex->cur == '\n') toy_lexer_advance_line(lex); + lex->cur++; + } + if (lex->cur < lex->end && *lex->cur == '"') lex->cur++; + return toy_lexer_emit(lex, TOK_STRING, start); + } + + return toy_lexer_emit(lex, TOK_EOF, start); +} + +ToyToken toy_lexer_peek(const ToyLexer* lex) { + ToyLexer tmp = *lex; + return toy_lexer_next(&tmp); +} diff --git a/lang/toy/lexer.h b/lang/toy/lexer.h @@ -0,0 +1,96 @@ +#ifndef CFREE_TOY_LEXER_H +#define CFREE_TOY_LEXER_H + +#include <cfree.h> +#include <stddef.h> +#include <stdint.h> + +typedef enum ToyTokenKind { + TOK_EOF = 0, + TOK_FN, + TOK_LET, + TOK_VAR, + TOK_IF, + TOK_ELSE, + TOK_WHILE, + TOK_SWITCH, + TOK_DEFAULT, + TOK_LABEL, + TOK_GOTO, + TOK_WITHIN, + TOK_BREAK, + TOK_CONTINUE, + TOK_RETURN, + TOK_TAIL, + TOK_TYPE, + TOK_PUB, + TOK_EXTERN, + TOK_ALIAS, + TOK_RECORD, + TOK_TUPLE, + TOK_ENUM, + TOK_AS, + TOK_AND, + TOK_OR, + TOK_INT, + TOK_IDENT, + TOK_NUMBER, + TOK_STRING, + TOK_LPAREN, + TOK_RPAREN, + TOK_LBRACE, + TOK_RBRACE, + TOK_LBRACKET, + TOK_RBRACKET, + TOK_COMMA, + TOK_SEMI, + TOK_COLON, + TOK_EQ, + TOK_PLUS, + TOK_MINUS, + TOK_STAR, + TOK_SLASH, + TOK_PERCENT, + TOK_LT, + TOK_GT, + TOK_LE, + TOK_GE, + TOK_EQEQ, + TOK_NE, + TOK_ANDAND, + TOK_PIPEPIPE, + TOK_AMPERSAND, + TOK_PIPE, + TOK_CARET, + TOK_TILDE, + TOK_SHL, + TOK_SHR, + TOK_BANG, + TOK_DOT, + TOK_DOTSTAR, + TOK_DOTDOTDOT, + TOK_AT, +} ToyTokenKind; + +typedef struct ToyToken { + ToyTokenKind kind; + CfreeSrcLoc loc; + const uint8_t* text; + size_t text_len; + int64_t int_value; + double float_value; + int is_float; +} ToyToken; + +typedef struct ToyLexer { + const uint8_t* cur; + const uint8_t* end; + const uint8_t* bol; + uint32_t line; +} ToyLexer; + +void toy_lexer_init(ToyLexer* lex, const uint8_t* data, size_t len); +ToyToken toy_lexer_next(ToyLexer* lex); +ToyToken toy_lexer_peek(const ToyLexer* lex); + +#endif diff --git a/lang/toy/literals.c b/lang/toy/literals.c @@ -0,0 +1,90 @@ +#include "internal.h" + +#include <string.h> + +void toy_emit_int_bytes(ToyParser* p, uint64_t v, uint8_t* buf, size_t n) { + size_t i; + (void)p; + for (i = 0; i < n; ++i) buf[i] = (uint8_t)(v >> (i * 8u)); +} + +int toy_parse_number_arg(ToyParser* p, int64_t* out) { + if (p->cur.kind != TOK_NUMBER || p->cur.is_float) { + toy_error(p, p->cur.loc, "expected integer constant"); + return 0; + } + *out = p->cur.int_value; + toy_parser_advance(p); + return 1; +} + +int toy_parse_string_sym(ToyParser* p, CfreeSym* out, size_t* len_out) { + char buf[256]; + size_t len; + if (p->cur.kind != TOK_STRING || p->cur.text_len < 2) { + toy_error(p, p->cur.loc, "expected string literal"); + return 0; + } + len = p->cur.text_len - 2; + if (len >= sizeof buf) { + toy_error(p, p->cur.loc, "string literal too long"); + return 0; + } + memcpy(buf, p->cur.text + 1, len); + buf[len] = '\0'; + *out = cfree_sym_intern(p->c, buf); + if (len_out) *len_out = len; + toy_parser_advance(p); + return 1; +} + +int toy_parse_string_bytes(ToyParser* p, uint8_t* out, size_t cap, + size_t* len_out) { + const uint8_t* cur; + const uint8_t* end; + size_t n = 0; + if (p->cur.kind != TOK_STRING || p->cur.text_len < 2) { + toy_error(p, p->cur.loc, "expected string literal"); + return 0; + } + cur = p->cur.text + 1; + end = p->cur.text + p->cur.text_len - 1; + while (cur < end) { + uint8_t c = *cur++; + if (c == '\\' && cur < end) { + uint8_t esc = *cur++; + if (esc == '0') c = 0; + else if (esc == 'n') c = '\n'; + else if (esc == 't') c = '\t'; + else if (esc == '"' || esc == '\\') c = esc; + else if (esc == 'x' && cur + 1 < end) { + uint8_t hi = *cur++; + uint8_t lo = *cur++; + uint8_t hv = (hi >= '0' && hi <= '9') ? (uint8_t)(hi - '0') + : (hi >= 'a' && hi <= 'f') ? (uint8_t)(hi - 'a' + 10) + : (hi >= 'A' && hi <= 'F') ? (uint8_t)(hi - 'A' + 10) + : 255; + uint8_t lv = (lo >= '0' && lo <= '9') ? (uint8_t)(lo - '0') + : (lo >= 'a' && lo <= 'f') ? (uint8_t)(lo - 'a' + 10) + : (lo >= 'A' && lo <= 'F') ? (uint8_t)(lo - 'A' + 10) + : 255; + if (hv == 255 || lv == 255) { + toy_error(p, p->cur.loc, "invalid hex escape"); + return 0; + } + c = (uint8_t)((hv << 4) | lv); + } else { + toy_error(p, p->cur.loc, "unknown string escape"); + return 0; + } + } + if (n >= cap) { + toy_error(p, p->cur.loc, "string literal too long"); + return 0; + } + out[n++] = c; + } + *len_out = n; + toy_parser_advance(p); + return 1; +} diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -0,0 +1,1645 @@ +#include "internal.h" + +#include <cfree/cg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Public CG API coverage goals for this frontend are tracked in + * doc/toy-todo.md. Keep this file aligned with include/cfree/cg.h rather than + * private CG implementation details. */ + +/* ============================================================ + * Parser (single-pass parse -> codegen) + * ============================================================ */ + +static int toy_parse_stmt(ToyParser* p); + +int toy_parse_block(ToyParser* p) { + size_t saved_nvars = p->nvars; + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{'"); + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + if (!toy_parse_stmt(p)) return 0; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}'"); + return 0; + } + p->nvars = saved_nvars; + return 1; +} + +static void toy_push_local_index(ToyParser* p, CfreeCgLocal slot, + uint64_t index) { + cfree_cg_push_local(p->cg, slot); + cfree_cg_push_int(p->cg, index, p->int_type); + cfree_cg_index(p->cg, 0); +} + +static int toy_check_source_value(ToyParser* p, CfreeCgTypeId expected_cg, + ToyTypeId expected_toy, + CfreeCgTypeId actual_cg, + ToyTypeId actual_toy, + const char* message) { + if (expected_toy != TOY_TYPE_NONE) { + if (!toy_type_accepts_type(p, expected_toy, actual_toy)) { + toy_error(p, p->cur.loc, message); + return 0; + } + return 1; + } + if (actual_cg != expected_cg) { + toy_error(p, p->cur.loc, message); + return 0; + } + return 1; +} + +static int toy_records_have_matching_storage(ToyParser* p, + CfreeCgTypeId expected, + CfreeCgTypeId actual) { + uint32_t i; + uint32_t nfields; + if (cfree_cg_type_kind(p->c, expected) != CFREE_CG_TYPE_RECORD || + cfree_cg_type_kind(p->c, actual) != CFREE_CG_TYPE_RECORD) { + return 0; + } + nfields = cfree_cg_type_record_nfields(p->c, expected); + if (nfields != cfree_cg_type_record_nfields(p->c, actual)) return 0; + for (i = 0; i < nfields; ++i) { + CfreeCgField expected_field; + CfreeCgField actual_field; + if (cfree_cg_type_record_field(p->c, expected, i, &expected_field, NULL) || + cfree_cg_type_record_field(p->c, actual, i, &actual_field, NULL)) { + return 0; + } + if (expected_field.name != actual_field.name || + expected_field.type != actual_field.type) { + return 0; + } + } + return 1; +} + +static int toy_store_record_value_as(ToyParser* p, CfreeCgTypeId src_ty, + CfreeCgLocal dst_slot, + CfreeCgTypeId dst_ty) { + CfreeCgLocal src_slot; + uint32_t i; + uint32_t nfields; + if (!toy_records_have_matching_storage(p, dst_ty, src_ty)) { + toy_error(p, p->cur.loc, "record storage mismatch"); + return 0; + } + src_slot = cfree_cg_local(p->cg, src_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, src_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, src_ty)); + nfields = cfree_cg_type_record_nfields(p->c, dst_ty); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, dst_ty, i, &field, NULL)) return 0; + cfree_cg_push_local(p->cg, dst_slot); + cfree_cg_field(p->cg, i); + cfree_cg_push_local(p->cg, src_slot); + cfree_cg_field(p->cg, i); + cfree_cg_load(p->cg, toy_mem_access(p, field.type)); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + } + return 1; +} + +static int toy_parse_array_initializer(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId arr_ty, + ToyTypeId arr_toy_type) { + CfreeCgTypeId elem_ty = cfree_cg_type_array_elem(p->c, arr_ty); + ToyTypeId elem_toy_type = toy_type_array_elem(p, arr_toy_type); + uint64_t count = cfree_cg_type_array_count(p->c, arr_ty); + uint64_t index = 0; + + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected array literal"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + CfreeCgTypeId expr_ty; + if (index >= count) { + toy_error(p, p->cur.loc, "too many array elements"); + return 0; + } + toy_push_local_index(p, slot, index); + expr_ty = toy_parse_expr(p); + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_check_source_value(p, elem_ty, elem_toy_type, expr_ty, + p->last_type, "array element type mismatch")) { + return 0; + } + if (expr_ty != elem_ty) cfree_cg_bitcast(p->cg, elem_ty); + cfree_cg_store(p->cg, toy_mem_access(p, elem_ty)); + index++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after array literal"); + return 0; + } + while (index < count) { + toy_push_local_index(p, slot, index); + cfree_cg_push_int(p->cg, 0, elem_ty); + cfree_cg_store(p->cg, toy_mem_access(p, elem_ty)); + index++; + } + return 1; +} + +static int toy_parse_record_initializer(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId record_ty, + ToyTypeId record_toy_type) { + uint32_t i, nfields = cfree_cg_type_record_nfields(p->c, record_ty); + ToyNamedType* named = toy_find_named_type_by_type(p, record_ty); + int positional = named && named->kind == TOY_NAMED_TUPLE; + (void)record_toy_type; + if (p->cur.kind == TOK_IDENT) toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected record literal"); + return 0; + } + + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, record_ty, i, &field, NULL) != 0) + return 0; + cfree_cg_push_local(p->cg, slot); + cfree_cg_field(p->cg, i); + cfree_cg_push_int(p->cg, 0, field.type); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + } + + if (positional) { + uint32_t field_index = 0; + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeCgField field; + CfreeCgTypeId expr_ty; + if (field_index >= nfields) { + toy_error(p, p->cur.loc, "too many tuple fields"); + return 0; + } + if (cfree_cg_type_record_field(p->c, record_ty, field_index, &field, + NULL) != 0) + return 0; + cfree_cg_push_local(p->cg, slot); + cfree_cg_field(p->cg, field_index); + expr_ty = toy_parse_expr(p); + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + { + ToyTypeId expected = + (named && field_index < named->nfields) + ? named->fields[field_index].toy_type + : TOY_TYPE_NONE; + if (!toy_check_source_value(p, field.type, expected, expr_ty, + p->last_type, + "tuple field type mismatch")) { + return 0; + } + } + if (expr_ty != field.type) cfree_cg_bitcast(p->cg, field.type); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + field_index++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after tuple literal"); + return 0; + } + return 1; + } + + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeSym field_name; + CfreeCgField field; + uint32_t field_index; + CfreeCgTypeId expr_ty; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected field name"); + return 0; + } + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' in record literal"); + return 0; + } + if (!toy_record_field_index(p, record_ty, field_name, &field_index, &field)) { + toy_error(p, p->cur.loc, "unknown record field"); + return 0; + } + cfree_cg_push_local(p->cg, slot); + cfree_cg_field(p->cg, field_index); + expr_ty = toy_parse_expr(p); + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + { + ToyTypeId expected = + (named && field_index < named->nfields) + ? named->fields[field_index].toy_type + : TOY_TYPE_NONE; + if (!toy_check_source_value(p, field.type, expected, expr_ty, + p->last_type, + "record field type mismatch")) { + return 0; + } + } + if (expr_ty != field.type) cfree_cg_bitcast(p->cg, field.type); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after record literal"); + return 0; + } + return 1; +} + +static int toy_parse_value_block_body_to_local(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type); + +static int toy_parse_if_initializer(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type) { + CfreeCgTypeId cond_ty; + CfreeCgIf it; + if (!toy_parser_match(p, TOK_IF)) return 0; + cond_ty = toy_parse_expr(p); + if (cond_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_emit_truthy(p, cond_ty)) return 0; + it = cfree_cg_if_begin(p->cg); + + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' in if expression"); + return 0; + } + if (!toy_parse_value_block_body_to_local(p, slot, result_ty, + result_toy_type)) return 0; + + cfree_cg_if_else(p->cg, it); + + if (!toy_parser_expect(p, TOK_ELSE) || + !toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected else block in if expression"); + return 0; + } + if (!toy_parse_value_block_body_to_local(p, slot, result_ty, + result_toy_type)) return 0; + + cfree_cg_if_end(p->cg, it); + return 1; +} + +static int toy_parse_value_block_body_to_local(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type) { + size_t saved_nvars = p->nvars; + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeCgTypeId arm_ty; + if (p->cur.kind == TOK_LET || p->cur.kind == TOK_VAR || + p->cur.kind == TOK_IF || p->cur.kind == TOK_WHILE || + p->cur.kind == TOK_SWITCH || p->cur.kind == TOK_LABEL || + p->cur.kind == TOK_GOTO || p->cur.kind == TOK_BREAK || + p->cur.kind == TOK_CONTINUE || p->cur.kind == TOK_RETURN || + p->cur.kind == TOK_LBRACE) { + if (!toy_parse_stmt(p)) { + p->nvars = saved_nvars; + return 0; + } + continue; + } + arm_ty = toy_parse_expr(p); + if (arm_ty == CFREE_CG_TYPE_NONE) { + p->nvars = saved_nvars; + return 0; + } + if (toy_parser_match(p, TOK_SEMI)) { + if (arm_ty != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) + cfree_cg_drop(p->cg); + continue; + } + if (!toy_check_source_value(p, result_ty, result_toy_type, arm_ty, + p->last_type, "block value type mismatch")) { + p->nvars = saved_nvars; + return 0; + } + if (arm_ty != result_ty) cfree_cg_bitcast(p->cg, result_ty); + cfree_cg_push_local(p->cg, slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, result_ty)); + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after value block"); + p->nvars = saved_nvars; + return 0; + } + p->nvars = saved_nvars; + return 1; + } + toy_error(p, p->cur.loc, "missing block value"); + p->nvars = saved_nvars; + return 0; +} + +static int toy_parse_value_block_to_local(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type) { + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected value block"); + return 0; + } + return toy_parse_value_block_body_to_local(p, slot, result_ty, + result_toy_type); +} + +static int toy_parse_switch_strategy_hint(ToyParser* p) { + if (p->cur.kind != TOK_AT || toy_lexer_peek(&p->lex).kind != TOK_LBRACKET) + return 1; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected switch strategy list"); + return 0; + } + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + CfreeSym name; + if (!toy_parse_attr_dot_name(p, &name)) return 0; + if (!toy_sym_is(p, name, "branch_chain") && + !toy_sym_is(p, name, "jump_table")) { + toy_error(p, p->cur.loc, "unknown switch strategy"); + return 0; + } + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after switch strategy list"); + return 0; + } + return 1; +} + +static int toy_parse_switch_initializer(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type) { + CfreeCgTypeId selector_ty; + CfreeCgLocal selector_slot; + CfreeCgLabel end_label; + ToyNamedType* selector_enum; + unsigned char* enum_seen = NULL; + int saw_default = 0; + size_t enum_seen_count = 0; + + if (!toy_parser_match(p, TOK_SWITCH)) return 0; + if (!toy_parse_switch_strategy_hint(p)) return 0; + selector_ty = toy_parse_expr(p); + if (selector_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_type_is_intlike(p, selector_ty)) { + toy_error(p, p->cur.loc, "switch selector must be integer-like"); + return 0; + } + selector_slot = cfree_cg_local(p->cg, selector_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, selector_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, selector_ty)); + end_label = cfree_cg_label_new(p->cg); + selector_enum = toy_find_named_type_by_type(p, selector_ty); + if (!selector_enum || selector_enum->kind != TOY_NAMED_ENUM) + selector_enum = NULL; + if (selector_enum) { + enum_seen = (unsigned char*)calloc( + selector_enum->nenum_values ? selector_enum->nenum_values : 1u, + sizeof *enum_seen); + if (!enum_seen) { + toy_error(p, p->cur.loc, "out of memory growing enum switch state"); + return 0; + } + } + + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after switch selector"); + free(enum_seen); + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeCgLabel arm_label = cfree_cg_label_new(p->cg); + CfreeCgLabel next_label = cfree_cg_label_new(p->cg); + if (p->cur.kind == TOK_DEFAULT) { + saw_default = 1; + toy_parser_advance(p); + cfree_cg_jump(p->cg, arm_label); + } else { + for (;;) { + int64_t value; + size_t i; + if (!toy_parse_switch_label_value(p, selector_ty, &value)) { + free(enum_seen); + return 0; + } + if (selector_enum) { + for (i = 0; i < selector_enum->nenum_values; ++i) { + if (selector_enum->enum_values[i].value == value) { + if (!enum_seen[i]) { + enum_seen[i] = 1; + enum_seen_count++; + } + break; + } + } + } + cfree_cg_push_local(p->cg, selector_slot); + cfree_cg_load(p->cg, toy_mem_access(p, selector_ty)); + cfree_cg_push_int(p->cg, (uint64_t)value, selector_ty); + cfree_cg_int_cmp(p->cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(p->cg, arm_label); + if (!toy_parser_match(p, TOK_COMMA)) break; + } + cfree_cg_jump(p->cg, next_label); + } + cfree_cg_label_place(p->cg, arm_label); + if (!toy_parse_value_block_to_local(p, slot, result_ty, result_toy_type)) { + free(enum_seen); + return 0; + } + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, next_label); + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after switch expression"); + free(enum_seen); + return 0; + } + if (!saw_default && + (!selector_enum || enum_seen_count != selector_enum->nenum_values)) { + toy_error(p, p->cur.loc, "expression switch requires default"); + free(enum_seen); + return 0; + } + cfree_cg_label_place(p->cg, end_label); + free(enum_seen); + return 1; +} + +static int toy_parse_while_initializer_named(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type, + CfreeSym label_name) { + CfreeCgTypeId explicit_ty; + ToyTypeId explicit_toy_type; + CfreeCgTypeId cond_ty; + CfreeCgScope scope; + CfreeCgLabel else_label; + + if (!toy_parser_match(p, TOK_WHILE)) return 0; + if (!toy_parser_expect(p, TOK_LT)) { + toy_error(p, p->cur.loc, "expected result type in while expression"); + return 0; + } + explicit_ty = toy_parse_type(p); + if (explicit_ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT)) + return 0; + explicit_toy_type = p->last_type; + if (!toy_check_source_value(p, result_ty, result_toy_type, explicit_ty, + explicit_toy_type, "while result type mismatch")) { + return 0; + } + if (!toy_parser_reserve(p, (void**)&p->scopes, &p->cap_scopes, + p->nscopes + 1u, sizeof *p->scopes, "scopes")) { + return 0; + } + scope = cfree_cg_scope_begin(p->cg, result_ty); + p->scopes[p->nscopes].name = label_name; + p->scopes[p->nscopes].kind = TOY_SCOPE_LOOP; + p->scopes[p->nscopes].cg_scope = scope; + p->scopes[p->nscopes].result_type = result_ty; + p->scopes[p->nscopes].result_toy_type = result_toy_type; + p->nscopes++; + else_label = cfree_cg_label_new(p->cg); + + cond_ty = toy_parse_expr(p); + if (cond_ty == CFREE_CG_TYPE_NONE) { + p->nscopes--; + return 0; + } + if (!toy_emit_truthy(p, cond_ty)) { + p->nscopes--; + return 0; + } + cfree_cg_branch_false(p->cg, else_label); + if (!toy_parse_block(p)) { + p->nscopes--; + return 0; + } + cfree_cg_continue(p->cg, scope); + cfree_cg_label_place(p->cg, else_label); + if (!toy_parser_expect(p, TOK_ELSE)) { + toy_error(p, p->cur.loc, "expected else in while expression"); + p->nscopes--; + return 0; + } + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected else block in while expression"); + p->nscopes--; + return 0; + } + { + CfreeCgTypeId else_ty = toy_parse_expr(p); + if (else_ty == CFREE_CG_TYPE_NONE) { + p->nscopes--; + return 0; + } + if (!toy_check_source_value(p, result_ty, result_toy_type, else_ty, + p->last_type, "while else type mismatch")) { + p->nscopes--; + return 0; + } + if (else_ty != result_ty) cfree_cg_bitcast(p->cg, result_ty); + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after while else"); + p->nscopes--; + return 0; + } + cfree_cg_scope_end(p->cg, scope); + p->nscopes--; + cfree_cg_push_local(p->cg, slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, result_ty)); + return 1; +} + +static int toy_parse_while_initializer(ToyParser* p, CfreeCgLocal slot, + CfreeCgTypeId result_ty, + ToyTypeId result_toy_type) { + return toy_parse_while_initializer_named(p, slot, result_ty, result_toy_type, + 0); +} + +static int toy_parse_let_stmt(ToyParser* p) { + CfreeSym name; + CfreeCgTypeId ty = CFREE_CG_TYPE_NONE; + CfreeCgTypeId init_ty = CFREE_CG_TYPE_NONE; + ToyTypeId toy_ty = TOY_TYPE_NONE; + CfreeCgLocal slot; + int has_init = 0; + int inferred = 0; + int is_var = p->cur.kind == TOK_VAR; + int is_static = 0; + int copy_record_init = 0; + toy_parser_advance(p); /* let/var */ + if (!toy_skip_attr_list_ex(p, &is_static)) return 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected identifier after 'let'"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + + if (toy_parser_match(p, TOK_COLON)) { + ty = toy_parse_type(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + toy_ty = p->last_type; + } else { + inferred = 1; + if (p->cur.kind != TOK_EQ) { + toy_error(p, p->cur.loc, "expected ':' or initializer after identifier"); + return 0; + } + } + + if (is_static) { + CfreeCgDecl decl; + CfreeCgDataDefAttrs data_attrs; + CfreeCgSym sym; + CfreeSym linkage_name; + char sym_name[64]; + uint8_t buf[1024]; + size_t n = (size_t)cfree_cg_type_size(p->c, ty); + int array_data_init = 0; + if (inferred) { + toy_error(p, p->cur.loc, "static local requires explicit type"); + return 0; + } + if (n > sizeof buf) { + toy_error(p, p->cur.loc, "static initializer too large"); + return 0; + } + memset(buf, 0, n); + if (toy_parser_match(p, TOK_EQ)) { + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + toy_emit_int_bytes(p, (uint64_t)p->cur.int_value, buf, n); + toy_parser_advance(p); + } else if (p->cur.kind == TOK_STRING && + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY && + cfree_cg_type_array_elem(p->c, ty) == + toy_builtin_type(p, CFREE_CG_BUILTIN_I8)) { + size_t len = 0; + if (!toy_parse_string_bytes(p, buf, sizeof buf, &len)) return 0; + if (len > n) { + toy_error(p, p->cur.loc, "string initializer too large"); + return 0; + } + } else if (p->cur.kind == TOK_LBRACKET && + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY && + toy_lexer_peek(&p->lex).kind == TOK_AT) { + array_data_init = 1; + } else { + toy_error(p, p->cur.loc, "expected constant static initializer"); + return 0; + } + } + snprintf(sym_name, sizeof sym_name, ".Ltoy_static_%u", p->static_counter++); + linkage_name = cfree_sym_intern(p->c, sym_name); + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = linkage_name; + decl.display_name = name; + decl.type = ty; + decl.sym.bind = CFREE_SB_LOCAL; + decl.sym.visibility = CFREE_CG_VIS_DEFAULT; + decl.as.object.tls_model = CFREE_CG_TLS_AUTO; + if (!is_var) decl.as.object.flags |= CFREE_CG_OBJ_READONLY; + sym = cfree_cg_decl(p->cg, decl); + if (sym == CFREE_CG_SYM_NONE) { + toy_error(p, p->cur.loc, "failed to declare static local"); + return 0; + } + if (!toy_add_static_local_typed(p, name, ty, toy_ty, sym, is_var)) + return 0; + memset(&data_attrs, 0, sizeof data_attrs); + cfree_cg_data_begin(p->cg, sym, data_attrs); + if (array_data_init) { + CfreeCgTypeId elem_ty = cfree_cg_type_array_elem(p->c, ty); + uint64_t total_size = cfree_cg_type_size(p->c, ty); + uint64_t pos = 0; + toy_parser_advance(p); /* '[' */ + while (p->cur.kind != TOK_RBRACKET && p->cur.kind != TOK_EOF) { + if (!toy_parse_data_array_builtin(p, elem_ty, total_size, &pos)) + return 0; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after static initializer"); + return 0; + } + if (pos < total_size) cfree_cg_data_zero(p->cg, total_size - pos); + } else { + cfree_cg_data_bytes(p->cg, buf, n); + } + cfree_cg_data_end(p->cg); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after static local"); + return 0; + } + return 1; + } + + if (!inferred && p->cur.kind == TOK_EQ && + ((cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY && + toy_lexer_peek(&p->lex).kind == TOK_LBRACKET) || + (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_RECORD && + (toy_lexer_peek(&p->lex).kind == TOK_IDENT || + toy_lexer_peek(&p->lex).kind == TOK_LBRACE)))) { + toy_parser_advance(p); /* = */ + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { + if (!toy_parse_array_initializer(p, slot, ty, toy_ty)) return 0; + } else { + if (!toy_parse_record_initializer(p, slot, ty, toy_ty)) return 0; + } + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + + if (!inferred && cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ENUM && + p->cur.kind == TOK_EQ && toy_lexer_peek(&p->lex).kind == TOK_DOT) { + ToyNamedType* named = toy_find_named_type_by_type(p, ty); + CfreeSym value_name; + size_t i; + int found = 0; + int64_t value = 0; + toy_parser_advance(p); /* = */ + toy_parser_advance(p); /* . */ + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected enum value"); + return 0; + } + value_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!named || named->kind != TOY_NAMED_ENUM) { + toy_error(p, p->cur.loc, "unknown enum type"); + return 0; + } + for (i = 0; i < named->nenum_values; ++i) { + if (named->enum_values[i].name == value_name) { + found = 1; + value = named->enum_values[i].value; + break; + } + } + if (!found) { + toy_error(p, p->cur.loc, "unknown enum value"); + return 0; + } + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + cfree_cg_push_local(p->cg, slot); + cfree_cg_push_int(p->cg, (uint64_t)value, ty); + cfree_cg_store(p->cg, toy_mem_access(p, ty)); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + + if (!inferred && p->cur.kind == TOK_EQ && + toy_lexer_peek(&p->lex).kind == TOK_IF) { + toy_parser_advance(p); /* = */ + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + if (!toy_parse_if_initializer(p, slot, ty, toy_ty)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + + if (!inferred && p->cur.kind == TOK_EQ && + toy_lexer_peek(&p->lex).kind == TOK_SWITCH) { + toy_parser_advance(p); /* = */ + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + if (!toy_parse_switch_initializer(p, slot, ty, toy_ty)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + + if (!inferred && p->cur.kind == TOK_EQ && + toy_lexer_peek(&p->lex).kind == TOK_WHILE) { + toy_parser_advance(p); /* = */ + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + if (!toy_parse_while_initializer(p, slot, ty, toy_ty)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + + if (!inferred && p->cur.kind == TOK_EQ) { + ToyLexer tmp_lex = p->lex; + ToyToken label_tok = toy_lexer_next(&tmp_lex); + ToyToken colon_tok = toy_lexer_next(&tmp_lex); + ToyToken while_tok = toy_lexer_next(&tmp_lex); + if (label_tok.kind == TOK_IDENT && colon_tok.kind == TOK_COLON && + while_tok.kind == TOK_WHILE) { + CfreeSym loop_label; + toy_parser_advance(p); /* = */ + loop_label = toy_tok_sym(p, p->cur); + toy_parser_advance(p); /* label */ + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after loop label"); + return 0; + } + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + if (!toy_parse_while_initializer_named(p, slot, ty, toy_ty, loop_label)) + return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; + } + } + + if (toy_parser_match(p, TOK_EQ)) { + ToyTypeId init_toy_type; + has_init = 1; + if (!inferred && toy_type_is_ptr(p, ty) && p->cur.kind == TOK_IDENT && + toy_sym_is(p, toy_tok_sym(p, p->cur), "NULL") && + toy_lexer_peek(&p->lex).kind == TOK_SEMI) { + toy_parser_advance(p); + cfree_cg_push_int(p->cg, 0, p->int_type); + cfree_cg_int_to_ptr(p->cg, ty); + init_ty = ty; + init_toy_type = toy_ty; + } else { + init_ty = toy_parse_expr(p); + if (init_ty == CFREE_CG_TYPE_NONE) return 0; + init_toy_type = p->last_type; + } + if (inferred) { + ty = init_ty; + toy_ty = init_toy_type; + } else { + if (!toy_check_source_value(p, ty, toy_ty, init_ty, init_toy_type, + "type mismatch in let initializer")) { + return 0; + } + if (init_ty != ty) { + if (toy_records_have_matching_storage(p, ty, init_ty)) + copy_record_init = 1; + else + cfree_cg_bitcast(p->cg, ty); + } + } + } + + slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); + if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; + + if (has_init) { + if (copy_record_init) { + if (!toy_store_record_value_as(p, init_ty, slot, ty)) return 0; + } else { + cfree_cg_push_local(p->cg, slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, ty)); + } + } + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after let"); + return 0; + } + return 1; +} + +static int toy_parse_if_stmt(ToyParser* p) { + CfreeCgIf it; + CfreeCgTypeId cond_ty; + toy_parser_advance(p); /* if */ + cond_ty = toy_parse_expr(p); + if (cond_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_emit_truthy(p, cond_ty)) return 0; + + it = cfree_cg_if_begin(p->cg); + + if (!toy_parse_block(p)) return 0; + + cfree_cg_if_else(p->cg, it); + + if (p->cur.kind == TOK_ELSE) { + toy_parser_advance(p); /* else */ + if (p->cur.kind == TOK_LBRACE) { + if (!toy_parse_block(p)) return 0; + } else { + if (!toy_parse_stmt(p)) return 0; + } + } + + cfree_cg_if_end(p->cg, it); + return 1; +} + +static int toy_parse_while_stmt_named(ToyParser* p, CfreeSym label_name) { + CfreeCgScope scope; + CfreeCgTypeId cond_ty; + + toy_parser_advance(p); /* while */ + + if (!toy_parser_reserve(p, (void**)&p->scopes, &p->cap_scopes, + p->nscopes + 1u, sizeof *p->scopes, "scopes")) { + return 0; + } + scope = cfree_cg_scope_begin(p->cg, CFREE_CG_TYPE_NONE); + p->scopes[p->nscopes].name = label_name; + p->scopes[p->nscopes].kind = TOY_SCOPE_LOOP; + p->scopes[p->nscopes].cg_scope = scope; + p->scopes[p->nscopes].result_type = CFREE_CG_TYPE_NONE; + p->scopes[p->nscopes].result_toy_type = TOY_TYPE_NONE; + p->nscopes++; + + cond_ty = toy_parse_expr(p); + if (cond_ty == CFREE_CG_TYPE_NONE) { + p->nscopes--; + return 0; + } + if (!toy_emit_truthy(p, cond_ty)) { + p->nscopes--; + return 0; + } + cfree_cg_break_false(p->cg, scope); + + if (!toy_parse_block(p)) { + p->nscopes--; + return 0; + } + + cfree_cg_continue(p->cg, scope); + cfree_cg_scope_end(p->cg, scope); + p->nscopes--; + return 1; +} + +static int toy_parse_while_stmt(ToyParser* p) { + return toy_parse_while_stmt_named(p, 0); +} + +static int toy_parse_switch_stmt_named(ToyParser* p, CfreeSym label_name) { + CfreeCgTypeId selector_ty; + CfreeCgLocal selector_slot; + CfreeCgLabel end_label; + CfreeCgScope scope; + + toy_parser_advance(p); /* switch */ + if (!toy_parse_switch_strategy_hint(p)) return 0; + selector_ty = toy_parse_expr(p); + if (selector_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_type_is_intlike(p, selector_ty)) { + toy_error(p, p->cur.loc, "switch selector must be integer-like"); + return 0; + } + selector_slot = cfree_cg_local(p->cg, selector_ty, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, selector_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, selector_ty)); + end_label = cfree_cg_label_new(p->cg); + if (!toy_parser_reserve(p, (void**)&p->scopes, &p->cap_scopes, + p->nscopes + 1u, sizeof *p->scopes, "scopes")) { + return 0; + } + scope = cfree_cg_scope_begin(p->cg, CFREE_CG_TYPE_NONE); + p->scopes[p->nscopes].name = label_name; + p->scopes[p->nscopes].kind = TOY_SCOPE_SWITCH; + p->scopes[p->nscopes].cg_scope = scope; + p->scopes[p->nscopes].result_type = CFREE_CG_TYPE_NONE; + p->scopes[p->nscopes].result_toy_type = TOY_TYPE_NONE; + p->nscopes++; + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after switch selector"); + p->nscopes--; + return 0; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + CfreeCgLabel arm_label = cfree_cg_label_new(p->cg); + CfreeCgLabel next_label = cfree_cg_label_new(p->cg); + if (p->cur.kind == TOK_DEFAULT) { + toy_parser_advance(p); + cfree_cg_jump(p->cg, arm_label); + } else { + for (;;) { + int64_t value; + if (!toy_parse_switch_label_value(p, selector_ty, &value)) { + p->nscopes--; + return 0; + } + cfree_cg_push_local(p->cg, selector_slot); + cfree_cg_load(p->cg, toy_mem_access(p, selector_ty)); + cfree_cg_push_int(p->cg, (uint64_t)value, selector_ty); + cfree_cg_int_cmp(p->cg, CFREE_CG_INT_EQ); + cfree_cg_branch_true(p->cg, arm_label); + if (!toy_parser_match(p, TOK_COMMA)) break; + } + cfree_cg_jump(p->cg, next_label); + } + cfree_cg_label_place(p->cg, arm_label); + if (!toy_parse_block(p)) { + p->nscopes--; + return 0; + } + cfree_cg_jump(p->cg, end_label); + cfree_cg_label_place(p->cg, next_label); + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after switch"); + p->nscopes--; + return 0; + } + cfree_cg_label_place(p->cg, end_label); + cfree_cg_scope_end(p->cg, scope); + p->nscopes--; + return 1; +} + +static int toy_parse_switch_stmt(ToyParser* p) { + return toy_parse_switch_stmt_named(p, 0); +} + +static int toy_add_goto_target(ToyParser* p, uint32_t* ntargets, + CfreeCgLabel label) { + if (!toy_parser_reserve(p, (void**)&p->goto_targets, + &p->cap_goto_targets, (size_t)*ntargets + 1u, + sizeof *p->goto_targets, "goto targets")) { + return 0; + } + p->goto_targets[*ntargets] = label; + (*ntargets)++; + return 1; +} + +static int toy_parse_label_decl_stmt(ToyParser* p) { + CfreeSym name; + toy_parser_advance(p); /* label */ + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected label name"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_declare_label(p, name)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after label declaration"); + return 0; + } + return 1; +} + +static int toy_parse_goto_stmt(ToyParser* p) { + CfreeCgTypeId target_ty; + CfreeCgTypeId void_ptr_ty = + cfree_cg_type_ptr(p->c, toy_builtin_type(p, CFREE_CG_BUILTIN_VOID), 0); + uint32_t ntargets = 0; + uint32_t i; + toy_parser_advance(p); /* goto */ + if (!toy_parser_expect(p, TOK_STAR)) { + toy_error(p, p->cur.loc, "expected computed goto target"); + return 0; + } + target_ty = toy_parse_expr(p); + if (target_ty == CFREE_CG_TYPE_NONE) return 0; + if (target_ty != void_ptr_ty) { + toy_error(p, p->cur.loc, "computed goto target must be *void"); + return 0; + } + if (toy_parser_match(p, TOK_WITHIN)) { + if (!toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected target list"); + return 0; + } + while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { + CfreeSym name; + ToyLabel* label; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected label in target list"); + return 0; + } + name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + label = toy_find_label(p, name); + if (!label) { + toy_error(p, p->cur.loc, "unknown label in target list"); + return 0; + } + if (!toy_add_goto_target(p, &ntargets, label->label)) + return 0; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after target list"); + return 0; + } + } + if (ntargets == 0) { + for (i = 0; i < p->nlabels; ++i) { + if (!toy_add_goto_target(p, &ntargets, p->labels[i].label)) + return 0; + } + } + cfree_cg_computed_goto(p->cg, p->goto_targets, ntargets); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after goto"); + return 0; + } + return 1; +} + +static int toy_parse_break_stmt(ToyParser* p) { + CfreeSym target_name = 0; + ToyScope* target_scope; + toy_parser_advance(p); /* break */ + if (p->nscopes == 0) { + toy_error(p, p->cur.loc, "break outside loop"); + return 0; + } + if (p->cur.kind == TOK_IDENT) { + CfreeSym maybe_target = toy_tok_sym(p, p->cur); + if (toy_find_scope(p, maybe_target)) { + target_name = maybe_target; + toy_parser_advance(p); + } + } + target_scope = toy_find_scope(p, target_name); + if (!target_scope) { + toy_error(p, p->cur.loc, "unknown break target"); + return 0; + } + if (p->cur.kind != TOK_SEMI) { + CfreeCgTypeId expr_ty = toy_parse_expr(p); + CfreeCgTypeId result_ty = target_scope->result_type; + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + if (result_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "break value type mismatch"); + return 0; + } + if (!toy_check_source_value(p, result_ty, target_scope->result_toy_type, + expr_ty, p->last_type, + "break value type mismatch")) + return 0; + if (expr_ty != result_ty) cfree_cg_bitcast(p->cg, result_ty); + cfree_cg_break(p->cg, target_scope->cg_scope); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after break"); + return 0; + } + return 1; + } + cfree_cg_break(p->cg, target_scope->cg_scope); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after break"); + return 0; + } + return 1; +} + +static int toy_parse_continue_stmt(ToyParser* p) { + CfreeSym target_name = 0; + ToyScope* target_scope; + toy_parser_advance(p); /* continue */ + if (p->nscopes == 0) { + toy_error(p, p->cur.loc, "continue outside loop"); + return 0; + } + if (p->cur.kind == TOK_IDENT) { + target_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + target_scope = toy_find_scope(p, target_name); + if (!target_scope) { + toy_error(p, p->cur.loc, "unknown continue target"); + return 0; + } + if (target_scope->kind != TOY_SCOPE_LOOP) { + toy_error(p, p->cur.loc, "continue target is not a loop"); + return 0; + } + } else { + target_scope = toy_find_innermost_loop_scope(p); + if (!target_scope) { + toy_error(p, p->cur.loc, "continue outside loop"); + return 0; + } + } + cfree_cg_continue(p->cg, target_scope->cg_scope); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after continue"); + return 0; + } + return 1; +} + +static int toy_parse_return_stmt(ToyParser* p) { + CfreeCgTypeId ty; + toy_parser_advance(p); /* return */ + if (toy_parser_match(p, TOK_TAIL)) { + CfreeSym name; + ToyFn* fn; + ToyToken call_tok; + CfreeCgTypeId fn_ty; + size_t nargs = 0; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected function name after tail"); + return 0; + } + call_tok = p->cur; + name = toy_tok_sym(p, p->cur); + fn = toy_find_fn(p, name); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LPAREN)) return 0; + if (fn) { + fn_ty = fn->type; + } else { + CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name); + if (callee_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, call_tok.loc, "undefined function in tail call"); + return 0; + } + fn_ty = toy_ptr_pointee_func_type(p, callee_ty); + if (fn_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, call_tok.loc, "tail callee is not a function pointer"); + return 0; + } + } + if (cfree_cg_type_func_is_variadic(p->c, fn_ty)) { + toy_error(p, call_tok.loc, "tail call to variadic function unsupported"); + return 0; + } + if (!toy_parse_call_args(p, call_tok, fn_ty, fn ? fn->toy_params : NULL, + fn ? fn->nparams : 0, &nargs)) return 0; + if (fn && !toy_type_accepts_type(p, p->cur_fn_ret_toy, fn->toy_ret)) { + toy_error(p, p->cur.loc, "tail call signature mismatch"); + return 0; + } + if (!fn && cfree_cg_type_func_ret(p->c, fn_ty) != p->cur_fn_ret) { + toy_error(p, p->cur.loc, "tail call signature mismatch"); + return 0; + } + if (fn) + cfree_cg_tail_call_symbol(p->cg, fn->sym, (uint32_t)nargs); + else + cfree_cg_tail_call(p->cg, (uint32_t)nargs, fn_ty); + if (!toy_parser_expect(p, TOK_SEMI)) return 0; + return 1; + } + if (p->cur.kind == TOK_SEMI) { + toy_parser_advance(p); + if (p->cur_fn_ret != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { + toy_error(p, p->cur.loc, "return without value in non-void function"); + return 0; + } + cfree_cg_ret_void(p->cg); + return 1; + } + if (p->cur_fn_ret != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { + int is_control_expr = p->cur.kind == TOK_IF || p->cur.kind == TOK_SWITCH || + p->cur.kind == TOK_WHILE; + CfreeSym loop_label = 0; + if (!is_control_expr && p->cur.kind == TOK_IDENT) { + ToyLexer tmp_lex = p->lex; + ToyToken colon_tok = toy_lexer_next(&tmp_lex); + ToyToken while_tok = toy_lexer_next(&tmp_lex); + if (colon_tok.kind == TOK_COLON && while_tok.kind == TOK_WHILE) { + is_control_expr = 1; + loop_label = toy_tok_sym(p, p->cur); + } + } + if (is_control_expr) { + CfreeCgLocal slot = + cfree_cg_local(p->cg, p->cur_fn_ret, toy_slot_attrs(0)); + if (p->cur.kind == TOK_IF) { + if (!toy_parse_if_initializer(p, slot, p->cur_fn_ret, + p->cur_fn_ret_toy)) return 0; + } else if (p->cur.kind == TOK_SWITCH) { + if (!toy_parse_switch_initializer(p, slot, p->cur_fn_ret, + p->cur_fn_ret_toy)) return 0; + } else if (p->cur.kind == TOK_WHILE) { + if (!toy_parse_while_initializer(p, slot, p->cur_fn_ret, + p->cur_fn_ret_toy)) return 0; + } else { + toy_parser_advance(p); /* label */ + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after loop label"); + return 0; + } + if (!toy_parse_while_initializer_named(p, slot, p->cur_fn_ret, + p->cur_fn_ret_toy, + loop_label)) { + return 0; + } + } + cfree_cg_push_local(p->cg, slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->cur_fn_ret)); + cfree_cg_ret(p->cg); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after return"); + return 0; + } + return 1; + } + } + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_check_source_value(p, p->cur_fn_ret, p->cur_fn_ret_toy, ty, + p->last_type, "return type mismatch")) + return 0; + if (ty != p->cur_fn_ret) cfree_cg_bitcast(p->cg, p->cur_fn_ret); + cfree_cg_ret(p->cg); + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after return"); + return 0; + } + return 1; +} + +static int toy_parse_expr_stmt(ToyParser* p) { + CfreeCgTypeId ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after expression"); + return 0; + } + if (ty != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) cfree_cg_drop(p->cg); + return 1; +} + +static int toy_parse_stmt(ToyParser* p) { + toy_set_loc(p); + if (p->cur.kind == TOK_LET || p->cur.kind == TOK_VAR) + return toy_parse_let_stmt(p); + if (p->cur.kind == TOK_IF) return toy_parse_if_stmt(p); + if (p->cur.kind == TOK_WHILE) return toy_parse_while_stmt(p); + if (p->cur.kind == TOK_SWITCH) return toy_parse_switch_stmt(p); + if (p->cur.kind == TOK_LABEL) return toy_parse_label_decl_stmt(p); + if (p->cur.kind == TOK_GOTO) return toy_parse_goto_stmt(p); + if (p->cur.kind == TOK_BREAK) return toy_parse_break_stmt(p); + if (p->cur.kind == TOK_CONTINUE) return toy_parse_continue_stmt(p); + if (p->cur.kind == TOK_RETURN) return toy_parse_return_stmt(p); + if (p->cur.kind == TOK_LBRACE) return toy_parse_block(p); + + /* Assignment or expression statement */ + if (p->cur.kind == TOK_IDENT && + toy_lexer_peek(&p->lex).kind == TOK_COLON) { + CfreeSym name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); /* ident */ + toy_parser_advance(p); /* : */ + if (p->cur.kind == TOK_WHILE) return toy_parse_while_stmt_named(p, name); + if (p->cur.kind == TOK_SWITCH) return toy_parse_switch_stmt_named(p, name); + { + ToyLabel* label = toy_declare_label(p, name); + if (!label) return 0; + cfree_cg_label_place(p->cg, label->label); + } + return 1; + } + + if (p->cur.kind == TOK_IDENT && + (toy_lexer_peek(&p->lex).kind == TOK_LBRACKET || + toy_lexer_peek(&p->lex).kind == TOK_DOTSTAR || + toy_lexer_peek(&p->lex).kind == TOK_DOT)) { + CfreeSym name = toy_tok_sym(p, p->cur); + CfreeCgTypeId lhs_ty; + ToyTypeId lhs_toy_type = TOY_TYPE_NONE; + CfreeCgTypeId root_ty = CFREE_CG_TYPE_NONE; + int root_mutable = 1; + toy_parser_advance(p); + { + ToyVar* v = toy_find_var(p, name); + ToyGlobal* g = toy_find_global(p, name); + if (v) { + root_ty = v->type; + lhs_toy_type = v->toy_type; + root_mutable = v->mutable; + } else if (g) { + root_ty = g->type; + lhs_toy_type = g->toy_type; + root_mutable = g->mutable; + } + if (v && cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD) { + toy_push_var_lvalue(p, v); + lhs_ty = v->type; + } else if (g && cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + lhs_ty = g->type; + } else { + lhs_ty = toy_push_named_rvalue(p, name); + } + } + if (lhs_ty == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "undefined variable in assignment"); + return 0; + } + + for (;;) { + if (toy_parser_match(p, TOK_LBRACKET)) { + CfreeCgTypeId idx_ty = toy_parse_expr(p); + if (idx_ty == CFREE_CG_TYPE_NONE) return 0; + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after index"); + return 0; + } + if (idx_ty != p->int_type) { + toy_error(p, p->cur.loc, "index must be i64"); + return 0; + } + if (cfree_cg_type_kind(p->c, lhs_ty) == CFREE_CG_TYPE_PTR) { + CfreeCgTypeId pointee = cfree_cg_type_ptr_pointee(p->c, lhs_ty); + ToyTypeId source_pointee = toy_type_pointee(p, lhs_toy_type); + if (cfree_cg_type_kind(p->c, pointee) == CFREE_CG_TYPE_ARRAY) { + CfreeCgLocal idx_slot = + cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_indirect(p->cg); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + lhs_ty = cfree_cg_type_array_elem(p->c, pointee); + lhs_toy_type = toy_type_array_elem(p, source_pointee); + } else { + lhs_ty = pointee; + lhs_toy_type = source_pointee; + } + } else if (cfree_cg_type_kind(p->c, lhs_ty) == CFREE_CG_TYPE_ARRAY) { + lhs_ty = cfree_cg_type_array_elem(p->c, lhs_ty); + lhs_toy_type = toy_type_array_elem(p, lhs_toy_type); + } else { + toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); + return 0; + } + cfree_cg_index(p->cg, 0); + continue; + } + if (toy_parser_match(p, TOK_DOTSTAR)) { + if (cfree_cg_type_kind(p->c, lhs_ty) != CFREE_CG_TYPE_PTR) { + toy_error(p, p->cur.loc, "cannot dereference non-pointer"); + return 0; + } + lhs_ty = cfree_cg_type_ptr_pointee(p->c, lhs_ty); + lhs_toy_type = toy_type_pointee(p, lhs_toy_type); + cfree_cg_indirect(p->cg); + continue; + } + if (toy_parser_match(p, TOK_DOT)) { + CfreeCgField field; + uint32_t field_index = 0; + ToyNamedType* named; + if (cfree_cg_type_kind(p->c, lhs_ty) == CFREE_CG_TYPE_PTR && + cfree_cg_type_kind(p->c, + cfree_cg_type_ptr_pointee(p->c, lhs_ty)) == + CFREE_CG_TYPE_RECORD) { + lhs_ty = cfree_cg_type_ptr_pointee(p->c, lhs_ty); + lhs_toy_type = toy_type_pointee(p, lhs_toy_type); + cfree_cg_indirect(p->cg); + } + if (cfree_cg_type_kind(p->c, lhs_ty) != CFREE_CG_TYPE_RECORD) { + toy_error(p, p->cur.loc, "field assignment on non-record"); + return 0; + } + named = toy_find_named_type_by_type(p, lhs_ty); + if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { + if (p->cur.int_value < 0 || + p->cur.int_value >= + (int64_t)cfree_cg_type_record_nfields(p->c, lhs_ty)) { + toy_error(p, p->cur.loc, "invalid tuple field"); + return 0; + } + field_index = (uint32_t)p->cur.int_value; + toy_parser_advance(p); + if (cfree_cg_type_record_field(p->c, lhs_ty, field_index, &field, + NULL) != 0) + return 0; + } else { + CfreeSym field_name; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected field name"); + return 0; + } + field_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_record_field_index(p, lhs_ty, field_name, &field_index, + &field)) { + toy_error(p, p->cur.loc, "unknown record field"); + return 0; + } + } + cfree_cg_field(p->cg, field_index); + lhs_ty = field.type; + lhs_toy_type = (named && field_index < named->nfields) + ? named->fields[field_index].toy_type + : TOY_TYPE_NONE; + continue; + } + break; + } + + if (!toy_parser_expect(p, TOK_EQ)) { + toy_error(p, p->cur.loc, "expected '=' in assignment"); + return 0; + } + if (!root_mutable && + cfree_cg_type_kind(p->c, root_ty) != CFREE_CG_TYPE_PTR) { + toy_error(p, p->cur.loc, "cannot assign to immutable local"); + return 0; + } + { + CfreeCgTypeId expr_ty = toy_parse_expr(p); + ToyTypeId expr_toy_type = p->last_type; + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + if (lhs_toy_type != TOY_TYPE_NONE) { + if (!toy_type_accepts_type(p, lhs_toy_type, expr_toy_type)) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + if (expr_ty != lhs_ty) cfree_cg_bitcast(p->cg, lhs_ty); + } else if (expr_ty != lhs_ty) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + cfree_cg_store(p->cg, toy_mem_access(p, lhs_ty)); + } + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after assignment"); + return 0; + } + return 1; + } + + if (p->cur.kind == TOK_IDENT && + toy_lexer_peek(&p->lex).kind == TOK_EQ) { + CfreeSym name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); /* ident */ + toy_parser_advance(p); /* = */ + CfreeCgTypeId expr_ty = toy_parse_expr(p); + ToyTypeId expr_toy_type = p->last_type; + if (expr_ty == CFREE_CG_TYPE_NONE) return 0; + { + ToyVar* v = toy_find_var(p, name); + if (v) { + if (!v->mutable) { + toy_error(p, p->cur.loc, "cannot assign to immutable local"); + return 0; + } + if (!toy_type_accepts_type(p, v->toy_type, expr_toy_type)) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + toy_push_var_lvalue(p, v); + cfree_cg_swap(p->cg); + if (expr_ty != v->type) cfree_cg_bitcast(p->cg, v->type); + cfree_cg_store(p->cg, toy_mem_access(p, v->type)); + } else { + ToyGlobal* g = toy_find_global(p, name); + if (!g) { + toy_error(p, p->cur.loc, "undefined variable in assignment"); + return 0; + } + if (!g->mutable) { + toy_error(p, p->cur.loc, "cannot assign to immutable global"); + return 0; + } + if (!toy_type_accepts_type(p, g->toy_type, expr_toy_type)) { + toy_error(p, p->cur.loc, "type mismatch in assignment"); + return 0; + } + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + cfree_cg_swap(p->cg); + if (expr_ty != g->type) cfree_cg_bitcast(p->cg, g->type); + cfree_cg_store(p->cg, toy_mem_access(p, g->type)); + } + } + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after assignment"); + return 0; + } + return 1; + } + + return toy_parse_expr_stmt(p); +} + +/* ============================================================ + * Program parsing + * ============================================================ */ + +int toy_parse_program(ToyParser* p) { + while (p->cur.kind != TOK_EOF) { + int is_extern = 0; + int is_pub = 0; + if (toy_parser_match(p, TOK_PUB)) { + is_pub = 1; + if (p->cur.kind != TOK_FN && p->cur.kind != TOK_LET && + p->cur.kind != TOK_VAR && p->cur.kind != TOK_ALIAS) { + toy_error(p, p->cur.loc, "expected declaration after pub"); + return 0; + } + } + if (toy_parser_match(p, TOK_EXTERN)) { + is_extern = 1; + if (p->cur.kind != TOK_FN && p->cur.kind != TOK_VAR && + p->cur.kind != TOK_LET) { + toy_error(p, p->cur.loc, "expected extern declaration"); + return 0; + } + } + if (p->cur.kind == TOK_FN) { + int r = toy_parse_fn(p, is_extern, is_pub); + if (r < 0) return 0; + if (r == 0) { + toy_error(p, p->cur.loc, "expected function declaration"); + return 0; + } + } else if (p->cur.kind == TOK_LET || p->cur.kind == TOK_VAR) { + if (!toy_parse_global_var(p, is_extern, is_pub)) return 0; + } else if (p->cur.kind == TOK_TYPE && !is_extern) { + if (!toy_parse_type_alias_decl(p)) return 0; + } else if (p->cur.kind == TOK_RECORD) { + if (!toy_parse_record_decl(p)) return 0; + } else if (p->cur.kind == TOK_TUPLE && !is_extern) { + if (!toy_parse_tuple_decl(p)) return 0; + } else if (p->cur.kind == TOK_ENUM && !is_extern) { + if (!toy_parse_enum_decl(p)) return 0; + } else if (p->cur.kind == TOK_ALIAS && !is_extern) { + if (!toy_parse_alias_decl(p, is_pub)) return 0; + } else { + toy_error(p, p->cur.loc, "expected function or global declaration"); + return 0; + } + } + return 1; +} diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -0,0 +1,198 @@ +#include "internal.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty) { + return p->types.id[ty]; +} + +void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, + const uint8_t* data, size_t len) { + toy_lexer_init(&p->lex, data, len); + p->cur = toy_lexer_next(&p->lex); + p->c = c; + p->cg = cg; + p->types = cfree_cg_builtin_types(c); + p->int_type = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); + p->int_ptr_type = cfree_cg_type_ptr(c, p->int_type, 0); + p->va_list_type = toy_builtin_type(p, CFREE_CG_BUILTIN_VARARG_STATE); + p->target = cfree_compiler_target(c); + p->nvars = 0; + p->cap_vars = 0; + p->vars = NULL; + p->nfns = 0; + p->cap_fns = 0; + p->fns = NULL; + p->nglobals = 0; + p->cap_globals = 0; + p->globals = NULL; + p->type_table.named = NULL; + p->type_table.count = 0; + p->type_table.cap = 0; + p->type_table.types = NULL; + p->type_table.ntypes = 0; + p->type_table.cap_types = 0; + toy_type_register_builtins(p); + p->nscopes = 0; + p->cap_scopes = 0; + p->scopes = NULL; + p->nlabels = 0; + p->cap_labels = 0; + p->labels = NULL; + p->goto_targets = NULL; + p->cap_goto_targets = 0; + p->cur_fn_ret = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + p->cur_fn_ret_toy = toy_type_from_cg(p, p->cur_fn_ret); + p->diag = cfree_compiler_diag_sink(c); + p->has_error = 0; + p->static_counter = 0; + p->expr_island_mask = 0; + p->last_type = TOY_TYPE_NONE; +} + +void toy_parser_dispose(ToyParser* p) { + size_t i; + free(p->vars); + for (i = 0; i < p->nfns; ++i) { + free(p->fns[i].params); + free(p->fns[i].toy_params); + } + free(p->fns); + free(p->globals); + for (i = 0; i < p->type_table.ntypes; ++i) free(p->type_table.types[i].params); + free(p->type_table.types); + for (i = 0; i < p->type_table.count; ++i) { + free(p->type_table.named[i].enum_values); + free(p->type_table.named[i].fields); + } + free(p->type_table.named); + free(p->scopes); + free(p->labels); + free(p->goto_targets); + p->vars = NULL; + p->fns = NULL; + p->globals = NULL; + p->type_table.types = NULL; + p->type_table.named = NULL; + p->scopes = NULL; + p->labels = NULL; + p->goto_targets = NULL; + p->nvars = p->nfns = p->nglobals = 0; + p->type_table.count = 0; + p->type_table.ntypes = 0; + p->nscopes = p->nlabels = 0; + p->cap_vars = p->cap_fns = p->cap_globals = 0; + p->type_table.cap = 0; + p->type_table.cap_types = 0; + p->cap_scopes = p->cap_labels = 0; + p->cap_goto_targets = 0; + p->last_type = TOY_TYPE_NONE; +} + +int toy_parser_reserve(ToyParser* p, void** items, size_t* cap, size_t want, + size_t elem_size, const char* what) { + size_t new_cap; + void* new_items; + if (want <= *cap) return 1; + new_cap = *cap ? *cap * 2u : 8u; + while (new_cap < want) new_cap *= 2u; + new_items = realloc(*items, new_cap * elem_size); + if (!new_items) { + toy_error(p, p->cur.loc, "out of memory growing %s", what); + return 0; + } + memset((uint8_t*)new_items + (*cap * elem_size), 0, + (new_cap - *cap) * elem_size); + *items = new_items; + *cap = new_cap; + return 1; +} + +void toy_parser_advance(ToyParser* p) { p->cur = toy_lexer_next(&p->lex); } + +int toy_parser_match(ToyParser* p, ToyTokenKind kind) { + if (p->cur.kind == kind) { + toy_parser_advance(p); + return 1; + } + return 0; +} + +int toy_parser_expect(ToyParser* p, ToyTokenKind kind) { + if (p->cur.kind == kind) { + toy_parser_advance(p); + return 1; + } + return 0; +} + +void toy_error(ToyParser* p, CfreeSrcLoc loc, const char* fmt, ...) { + va_list ap; + p->has_error = 1; + if (!p->diag) return; + va_start(ap, fmt); + p->diag->emit(p->diag, CFREE_DIAG_ERROR, loc, fmt, ap); + va_end(ap); +} + +void toy_set_loc(ToyParser* p) { + if (p->cg) cfree_cg_set_loc(p->cg, p->cur.loc); +} + +CfreeSym toy_tok_sym(ToyParser* p, ToyToken tok) { + char buf[64]; + if (tok.text_len >= sizeof(buf)) { + toy_error(p, tok.loc, "identifier too long"); + return 0; + } + memcpy(buf, tok.text, tok.text_len); + buf[tok.text_len] = '\0'; + return cfree_sym_intern(p->c, buf); +} + +int toy_sym_is(ToyParser* p, CfreeSym sym, const char* name) { + return sym == cfree_sym_intern(p->c, name); +} + +int toy_skip_attr_list_ex(ToyParser* p, int* has_static) { + int bracket_depth = 0; + int paren_depth = 0; + if (has_static) *has_static = 0; + if (!toy_parser_match(p, TOK_AT)) return 1; + if (!toy_parser_expect(p, TOK_LBRACKET)) { + toy_error(p, p->cur.loc, "expected '[' after '@'"); + return 0; + } + bracket_depth = 1; + while (p->cur.kind != TOK_EOF && bracket_depth > 0) { + if (p->cur.kind == TOK_DOT) { + toy_parser_advance(p); + if (p->cur.kind == TOK_IDENT && has_static && p->cur.text_len == 6 && + memcmp(p->cur.text, "static", 6) == 0) { + *has_static = 1; + } + continue; + } + if (p->cur.kind == TOK_LBRACKET && paren_depth == 0) + bracket_depth++; + else if (p->cur.kind == TOK_RBRACKET && paren_depth == 0) + bracket_depth--; + else if (p->cur.kind == TOK_LPAREN) + paren_depth++; + else if (p->cur.kind == TOK_RPAREN && paren_depth > 0) + paren_depth--; + toy_parser_advance(p); + } + if (bracket_depth != 0) { + toy_error(p, p->cur.loc, "unterminated attribute list"); + return 0; + } + return 1; +} + +int toy_skip_attr_list(ToyParser* p) { + return toy_skip_attr_list_ex(p, NULL); +} diff --git a/lang/toy/symbols.c b/lang/toy/symbols.c @@ -0,0 +1,216 @@ +#include "internal.h" + +#include <stdlib.h> + +ToyVar* toy_find_var(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->nvars; i > 0; --i) { + if (p->vars[i - 1].name == name) return &p->vars[i - 1]; + } + return NULL; +} + +int toy_add_local(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + CfreeCgLocal slot, int mutable) { + return toy_add_local_typed(p, name, ty, toy_type_from_cg(p, ty), slot, + mutable); +} + +int toy_add_local_typed(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + ToyTypeId toy_type, CfreeCgLocal slot, int mutable) { + if (!toy_parser_reserve(p, (void**)&p->vars, &p->cap_vars, p->nvars + 1u, + sizeof *p->vars, "locals")) { + return 0; + } + p->vars[p->nvars].name = name; + p->vars[p->nvars].type = ty; + p->vars[p->nvars].toy_type = + toy_type != TOY_TYPE_NONE ? toy_type : toy_type_from_cg(p, ty); + p->vars[p->nvars].local = slot; + p->vars[p->nvars].static_sym = CFREE_CG_SYM_NONE; + p->vars[p->nvars].is_static = 0; + p->vars[p->nvars].mutable = mutable; + p->nvars++; + return 1; +} + +int toy_add_static_local(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + CfreeCgSym sym, int mutable) { + return toy_add_static_local_typed(p, name, ty, toy_type_from_cg(p, ty), sym, + mutable); +} + +int toy_add_static_local_typed(ToyParser* p, CfreeSym name, CfreeCgTypeId ty, + ToyTypeId toy_type, CfreeCgSym sym, + int mutable) { + if (!toy_parser_reserve(p, (void**)&p->vars, &p->cap_vars, p->nvars + 1u, + sizeof *p->vars, "locals")) { + return 0; + } + p->vars[p->nvars].name = name; + p->vars[p->nvars].type = ty; + p->vars[p->nvars].toy_type = + toy_type != TOY_TYPE_NONE ? toy_type : toy_type_from_cg(p, ty); + p->vars[p->nvars].local = CFREE_CG_LOCAL_NONE; + p->vars[p->nvars].static_sym = sym; + p->vars[p->nvars].is_static = 1; + p->vars[p->nvars].mutable = mutable; + p->nvars++; + return 1; +} + +ToyFn* toy_find_fn(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->nfns; i > 0; --i) { + if (p->fns[i - 1].name == name) return &p->fns[i - 1]; + } + return NULL; +} + +ToyFn* toy_add_fn(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, CfreeCgTypeId ret, + const CfreeCgTypeId* params, size_t nparams, int variadic) { + return toy_add_fn_typed(p, name, sym, type, toy_type_from_cg(p, type), ret, + toy_type_from_cg(p, ret), params, NULL, nparams, + variadic); +} + +ToyFn* toy_add_fn_typed(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, ToyTypeId toy_type, + CfreeCgTypeId ret, ToyTypeId toy_ret, + const CfreeCgTypeId* params, + const ToyTypeId* toy_params, size_t nparams, + int variadic) { + ToyFn* fn; + size_t i; + if (!toy_parser_reserve(p, (void**)&p->fns, &p->cap_fns, p->nfns + 1u, + sizeof *p->fns, "functions")) { + return NULL; + } + fn = &p->fns[p->nfns]; + fn->params = NULL; + fn->toy_params = NULL; + if (nparams != 0) { + fn->params = (CfreeCgTypeId*)calloc(nparams, sizeof *fn->params); + fn->toy_params = (ToyTypeId*)calloc(nparams, sizeof *fn->toy_params); + if (!fn->params || !fn->toy_params) { + free(fn->params); + free(fn->toy_params); + fn->params = NULL; + fn->toy_params = NULL; + toy_error(p, p->cur.loc, "out of memory growing function parameters"); + return NULL; + } + } + fn->name = name; + fn->sym = sym; + fn->type = type; + fn->toy_type = + toy_type != TOY_TYPE_NONE ? toy_type : toy_type_from_cg(p, type); + fn->ret = ret; + fn->toy_ret = toy_ret != TOY_TYPE_NONE ? toy_ret : toy_type_from_cg(p, ret); + fn->nparams = nparams; + fn->variadic = variadic; + for (i = 0; i < nparams; ++i) { + fn->params[i] = params[i]; + fn->toy_params[i] = + (toy_params && toy_params[i] != TOY_TYPE_NONE) + ? toy_params[i] + : toy_type_from_cg(p, params[i]); + } + p->nfns++; + return fn; +} + +ToyGlobal* toy_find_global(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->nglobals; i > 0; --i) { + if (p->globals[i - 1].name == name) return &p->globals[i - 1]; + } + return NULL; +} + +int toy_add_global(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, int mutable) { + return toy_add_global_typed(p, name, sym, type, toy_type_from_cg(p, type), + mutable); +} + +int toy_add_global_typed(ToyParser* p, CfreeSym name, CfreeCgSym sym, + CfreeCgTypeId type, ToyTypeId toy_type, + int mutable) { + if (!toy_parser_reserve(p, (void**)&p->globals, &p->cap_globals, + p->nglobals + 1u, sizeof *p->globals, "globals")) { + return 0; + } + p->globals[p->nglobals].name = name; + p->globals[p->nglobals].sym = sym; + p->globals[p->nglobals].type = type; + p->globals[p->nglobals].toy_type = + toy_type != TOY_TYPE_NONE ? toy_type : toy_type_from_cg(p, type); + p->globals[p->nglobals].mutable = mutable; + p->nglobals++; + return 1; +} + +ToyLabel* toy_find_label(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->nlabels; i > 0; --i) { + if (p->labels[i - 1].name == name) return &p->labels[i - 1]; + } + return NULL; +} + +ToyLabel* toy_declare_label(ToyParser* p, CfreeSym name) { + ToyLabel* existing = toy_find_label(p, name); + if (existing) return existing; + if (!toy_parser_reserve(p, (void**)&p->labels, &p->cap_labels, + p->nlabels + 1u, sizeof *p->labels, "labels")) + return NULL; + p->labels[p->nlabels].name = name; + p->labels[p->nlabels].label = cfree_cg_label_new(p->cg); + return &p->labels[p->nlabels++]; +} + +ToyScope* toy_find_scope(ToyParser* p, CfreeSym name) { + size_t i; + if (p->nscopes == 0) return NULL; + if (!name) return &p->scopes[p->nscopes - 1]; + for (i = p->nscopes; i > 0; --i) { + if (p->scopes[i - 1].name == name) return &p->scopes[i - 1]; + } + return NULL; +} + +ToyScope* toy_find_innermost_loop_scope(ToyParser* p) { + size_t i; + for (i = p->nscopes; i > 0; --i) { + if (p->scopes[i - 1].kind == TOY_SCOPE_LOOP) + return &p->scopes[i - 1]; + } + return NULL; +} + +void toy_push_var_lvalue(ToyParser* p, const ToyVar* v) { + if (v->is_static) + cfree_cg_push_symbol_lvalue(p->cg, v->static_sym, 0); + else + cfree_cg_push_local(p->cg, v->local); +} + +void toy_push_var_addr(ToyParser* p, const ToyVar* v) { + if (v->is_static) + cfree_cg_push_symbol_addr(p->cg, v->static_sym, 0); + else + cfree_cg_push_local_addr(p->cg, v->local); +} + +CfreeCgSym toy_find_decl_sym(ToyParser* p, CfreeSym name) { + ToyGlobal* g = toy_find_global(p, name); + if (g) return g->sym; + { + ToyFn* fn = toy_find_fn(p, name); + if (fn) return fn->sym; + } + return CFREE_CG_SYM_NONE; +} diff --git a/lang/toy/toy.c b/lang/toy/toy.c @@ -1,2967 +0,0 @@ -#include "toy.h" - -#include <cfree/cg.h> -#include <stdarg.h> -#include <stddef.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - -/* Public CG API coverage goals for this frontend are tracked in - * doc/toy-todo.md. Keep this file aligned with include/cfree/cg.h rather than - * private CG implementation details. */ - -/* ============================================================ - * Lexer / token iterator - * ============================================================ */ - -typedef enum ToyTokenKind { - TOK_EOF = 0, - TOK_FN, - TOK_LET, - TOK_VAR, - TOK_IF, - TOK_ELSE, - TOK_WHILE, - TOK_BREAK, - TOK_CONTINUE, - TOK_RETURN, - TOK_TAIL, - TOK_TYPE, - TOK_AS, - TOK_INT, - TOK_IDENT, - TOK_NUMBER, - TOK_STRING, - TOK_LPAREN, - TOK_RPAREN, - TOK_LBRACE, - TOK_RBRACE, - TOK_LBRACKET, - TOK_RBRACKET, - TOK_COMMA, - TOK_SEMI, - TOK_COLON, - TOK_EQ, - TOK_PLUS, - TOK_MINUS, - TOK_STAR, - TOK_SLASH, - TOK_PERCENT, - TOK_LT, - TOK_GT, - TOK_LE, - TOK_GE, - TOK_EQEQ, - TOK_NE, - TOK_ANDAND, - TOK_PIPEPIPE, - TOK_AMPERSAND, - TOK_PIPE, - TOK_CARET, - TOK_TILDE, - TOK_SHL, - TOK_SHR, - TOK_BANG, - TOK_DOT, - TOK_DOTSTAR, - TOK_DOTDOTDOT, -} ToyTokenKind; - -typedef struct ToyToken { - ToyTokenKind kind; - CfreeSrcLoc loc; - const uint8_t* text; - size_t text_len; - int64_t int_value; - double float_value; - int is_float; -} ToyToken; - -typedef struct ToyLexer { - const uint8_t* cur; - const uint8_t* end; - const uint8_t* bol; - uint32_t line; -} ToyLexer; - -static void toy_lexer_init(ToyLexer* lex, const uint8_t* data, size_t len) { - lex->cur = data; - lex->end = data + len; - lex->bol = data; - lex->line = 1; -} - -static int toy_is_space(uint8_t c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || - c == '\v'; -} - -static int toy_is_digit(uint8_t c) { return c >= '0' && c <= '9'; } - -static int toy_is_alpha(uint8_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -static int toy_is_alnum(uint8_t c) { - return toy_is_alpha(c) || toy_is_digit(c); -} - -static void toy_lexer_advance_line(ToyLexer* lex) { - lex->bol = lex->cur + 1; - lex->line++; -} - -static void toy_skip_ws(ToyLexer* lex) { - while (lex->cur < lex->end && toy_is_space(*lex->cur)) { - if (*lex->cur == '\n') toy_lexer_advance_line(lex); - lex->cur++; - } -} - -static ToyToken toy_lexer_emit(ToyLexer* lex, ToyTokenKind kind, - const uint8_t* start) { - ToyToken tok; - tok.kind = kind; - tok.loc.file_id = 0; - tok.loc.line = lex->line; - tok.loc.col = (uint32_t)(start - lex->bol) + 1; - tok.text = start; - tok.text_len = (size_t)(lex->cur - start); - tok.int_value = 0; - tok.float_value = 0.0; - tok.is_float = 0; - return tok; -} - -static ToyToken toy_lexer_next(ToyLexer* lex) { - const uint8_t* start; - ToyToken tok; - - toy_skip_ws(lex); - start = lex->cur; - if (lex->cur >= lex->end) { - tok.kind = TOK_EOF; - tok.loc.file_id = 0; - tok.loc.line = lex->line; - tok.loc.col = (uint32_t)(start - lex->bol) + 1; - tok.text = start; - tok.text_len = 0; - tok.int_value = 0; - tok.float_value = 0.0; - tok.is_float = 0; - return tok; - } - - uint8_t c = *lex->cur++; - - switch (c) { - case '(': - return toy_lexer_emit(lex, TOK_LPAREN, start); - case ')': - return toy_lexer_emit(lex, TOK_RPAREN, start); - case '{': - return toy_lexer_emit(lex, TOK_LBRACE, start); - case '}': - return toy_lexer_emit(lex, TOK_RBRACE, start); - case '[': - return toy_lexer_emit(lex, TOK_LBRACKET, start); - case ']': - return toy_lexer_emit(lex, TOK_RBRACKET, start); - case ',': - return toy_lexer_emit(lex, TOK_COMMA, start); - case ';': - return toy_lexer_emit(lex, TOK_SEMI, start); - case ':': - return toy_lexer_emit(lex, TOK_COLON, start); - case '+': - return toy_lexer_emit(lex, TOK_PLUS, start); - case '*': - return toy_lexer_emit(lex, TOK_STAR, start); - case '/': - return toy_lexer_emit(lex, TOK_SLASH, start); - case '%': - return toy_lexer_emit(lex, TOK_PERCENT, start); - case '&': - if (lex->cur < lex->end && *lex->cur == '&') { - lex->cur++; - return toy_lexer_emit(lex, TOK_ANDAND, start); - } - return toy_lexer_emit(lex, TOK_AMPERSAND, start); - case '|': - if (lex->cur < lex->end && *lex->cur == '|') { - lex->cur++; - return toy_lexer_emit(lex, TOK_PIPEPIPE, start); - } - return toy_lexer_emit(lex, TOK_PIPE, start); - case '^': - return toy_lexer_emit(lex, TOK_CARET, start); - case '~': - return toy_lexer_emit(lex, TOK_TILDE, start); - case '=': - if (lex->cur < lex->end && *lex->cur == '=') { - lex->cur++; - return toy_lexer_emit(lex, TOK_EQEQ, start); - } - return toy_lexer_emit(lex, TOK_EQ, start); - case '!': - if (lex->cur < lex->end && *lex->cur == '=') { - lex->cur++; - return toy_lexer_emit(lex, TOK_NE, start); - } - return toy_lexer_emit(lex, TOK_BANG, start); - case '<': - if (lex->cur < lex->end && *lex->cur == '<') { - lex->cur++; - return toy_lexer_emit(lex, TOK_SHL, start); - } - if (lex->cur < lex->end && *lex->cur == '=') { - lex->cur++; - return toy_lexer_emit(lex, TOK_LE, start); - } - return toy_lexer_emit(lex, TOK_LT, start); - case '>': - if (lex->cur < lex->end && *lex->cur == '>') { - lex->cur++; - return toy_lexer_emit(lex, TOK_SHR, start); - } - if (lex->cur < lex->end && *lex->cur == '=') { - lex->cur++; - return toy_lexer_emit(lex, TOK_GE, start); - } - return toy_lexer_emit(lex, TOK_GT, start); - case '-': - return toy_lexer_emit(lex, TOK_MINUS, start); - case '.': - if (lex->cur + 1 < lex->end && lex->cur[0] == '.' && - lex->cur[1] == '.') { - lex->cur += 2; - return toy_lexer_emit(lex, TOK_DOTDOTDOT, start); - } - if (lex->cur < lex->end && *lex->cur == '*') { - lex->cur++; - return toy_lexer_emit(lex, TOK_DOTSTAR, start); - } - return toy_lexer_emit(lex, TOK_DOT, start); - } - - if (toy_is_digit(c)) { - int64_t v = (int64_t)(c - '0'); - int is_float = 0; - while (lex->cur < lex->end && toy_is_digit(*lex->cur)) { - v = v * 10 + (int64_t)(*lex->cur - '0'); - lex->cur++; - } - if (lex->cur < lex->end && *lex->cur == '.' && - lex->cur + 1 < lex->end && toy_is_digit(lex->cur[1])) { - is_float = 1; - lex->cur++; - while (lex->cur < lex->end && toy_is_digit(*lex->cur)) lex->cur++; - } - tok = toy_lexer_emit(lex, TOK_NUMBER, start); - tok.int_value = v; - tok.is_float = is_float; - if (is_float) { - char buf[64]; - size_t len = tok.text_len; - if (len >= sizeof buf) len = sizeof buf - 1; - memcpy(buf, tok.text, len); - buf[len] = '\0'; - tok.float_value = strtod(buf, NULL); - } - return tok; - } - - if (toy_is_alpha(c)) { - while (lex->cur < lex->end && toy_is_alnum(*lex->cur)) lex->cur++; - size_t len = (size_t)(lex->cur - start); - ToyTokenKind kind = TOK_IDENT; - if (len == 2 && start[0] == 'f' && start[1] == 'n') - kind = TOK_FN; - else if (len == 2 && start[0] == 'a' && start[1] == 's') - kind = TOK_AS; - else if (len == 3 && start[0] == 'v' && start[1] == 'a' && start[2] == 'r') - kind = TOK_VAR; - else if (len == 3 && start[0] == 'i' && start[1] == 'n' && start[2] == 't') - kind = TOK_INT; - else if (len == 3 && start[0] == 'l' && start[1] == 'e' && start[2] == 't') - kind = TOK_LET; - else if (len == 2 && start[0] == 'i' && start[1] == 'f') - kind = TOK_IF; - else if (len == 4 && start[0] == 'e' && start[1] == 'l' && - start[2] == 's' && start[3] == 'e') - kind = TOK_ELSE; - else if (len == 5 && start[0] == 'w' && start[1] == 'h' && - start[2] == 'i' && start[3] == 'l' && start[4] == 'e') - kind = TOK_WHILE; - else if (len == 5 && start[0] == 'b' && start[1] == 'r' && - start[2] == 'e' && start[3] == 'a' && start[4] == 'k') - kind = TOK_BREAK; - else if (len == 8 && start[0] == 'c' && start[1] == 'o' && - start[2] == 'n' && start[3] == 't' && start[4] == 'i' && - start[5] == 'n' && start[6] == 'u' && start[7] == 'e') - kind = TOK_CONTINUE; - else if (len == 6 && start[0] == 'r' && start[1] == 'e' && - start[2] == 't' && start[3] == 'u' && start[4] == 'r' && - start[5] == 'n') - kind = TOK_RETURN; - else if (len == 4 && start[0] == 't' && start[1] == 'a' && - start[2] == 'i' && start[3] == 'l') - kind = TOK_TAIL; - else if (len == 4 && start[0] == 't' && start[1] == 'y' && - start[2] == 'p' && start[3] == 'e') - kind = TOK_TYPE; - return toy_lexer_emit(lex, kind, start); - } - - if (c == '"') { - while (lex->cur < lex->end && *lex->cur != '"') { - if (*lex->cur == '\n') toy_lexer_advance_line(lex); - lex->cur++; - } - if (lex->cur < lex->end && *lex->cur == '"') lex->cur++; - return toy_lexer_emit(lex, TOK_STRING, start); - } - - return toy_lexer_emit(lex, TOK_EOF, start); -} - -/* Peek at the next token without consuming. */ -static ToyToken toy_lexer_peek(const ToyLexer* lex) { - ToyLexer tmp = *lex; - return toy_lexer_next(&tmp); -} - -/* ============================================================ - * Parser (single-pass parse -> codegen) - * ============================================================ */ - -#define TOY_MAX_VARS 64 -#define TOY_MAX_FNS 32 -#define TOY_MAX_SCOPES 16 -#define TOY_MAX_PARAMS 16 -#define TOY_MAX_GLOBALS 32 - -typedef struct ToyVar { - CfreeSym name; - CfreeCgTypeId type; - CfreeCgLocal local; -} ToyVar; - -typedef struct ToyFn { - CfreeSym name; - CfreeCgSym sym; - CfreeCgTypeId type; - CfreeCgTypeId ret; - CfreeCgTypeId params[TOY_MAX_PARAMS]; - size_t nparams; - int variadic; -} ToyFn; - -typedef struct ToyGlobal { - CfreeSym name; - CfreeCgSym sym; - CfreeCgTypeId type; - int mutable; -} ToyGlobal; - -typedef struct ToyScope { - CfreeCgScope cg_scope; -} ToyScope; - -typedef struct ToyParser { - ToyLexer lex; - ToyToken cur; - CfreeCompiler* c; - CfreeCg* cg; - CfreeCgBuiltinTypes types; - CfreeCgTypeId int_type; - CfreeCgTypeId int_ptr_type; - CfreeCgTypeId va_list_type; - CfreeCgTypeId pair_type; - CfreeCgTypeId pair_ptr_type; - CfreeTarget target; - - ToyVar vars[TOY_MAX_VARS]; - size_t nvars; - - ToyFn fns[TOY_MAX_FNS]; - size_t nfns; - - ToyGlobal globals[TOY_MAX_GLOBALS]; - size_t nglobals; - - ToyScope scopes[TOY_MAX_SCOPES]; - size_t nscopes; - - CfreeCgTypeId cur_fn_ret; - CfreeDiagSink* diag; - int has_error; -} ToyParser; - -static CfreeCgTypeId toy_builtin_type(ToyParser* p, CfreeCgBuiltinType ty) { - return p->types.id[ty]; -} - -static void toy_parser_init(ToyParser* p, CfreeCompiler* c, CfreeCg* cg, - const uint8_t* data, size_t len) { - toy_lexer_init(&p->lex, data, len); - p->cur = toy_lexer_next(&p->lex); - p->c = c; - p->cg = cg; - p->types = cfree_cg_builtin_types(c); - p->int_type = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); - p->int_ptr_type = cfree_cg_type_ptr(c, p->int_type, 0); - p->va_list_type = toy_builtin_type(p, CFREE_CG_BUILTIN_VARARG_STATE); - p->target = cfree_compiler_target(c); - { - CfreeCgField fields[2]; - memset(fields, 0, sizeof fields); - fields[0].name = cfree_sym_intern(c, "a"); - fields[0].type = p->int_type; - fields[1].name = cfree_sym_intern(c, "b"); - fields[1].type = p->int_type; - p->pair_type = cfree_cg_type_record(c, cfree_sym_intern(c, "Pair"), - fields, 2); - p->pair_ptr_type = cfree_cg_type_ptr(c, p->pair_type, 0); - } - p->nvars = 0; - p->nfns = 0; - p->nglobals = 0; - p->nscopes = 0; - p->cur_fn_ret = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - p->diag = cfree_compiler_diag_sink(c); - p->has_error = 0; -} - -static void toy_parser_advance(ToyParser* p) { - p->cur = toy_lexer_next(&p->lex); -} - -static int toy_parser_match(ToyParser* p, ToyTokenKind kind) { - if (p->cur.kind == kind) { - toy_parser_advance(p); - return 1; - } - return 0; -} - -static int toy_parser_expect(ToyParser* p, ToyTokenKind kind) { - if (p->cur.kind == kind) { - toy_parser_advance(p); - return 1; - } - return 0; -} - -static void toy_error(ToyParser* p, CfreeSrcLoc loc, const char* fmt, ...) { - va_list ap; - p->has_error = 1; - if (!p->diag) return; - va_start(ap, fmt); - p->diag->emit(p->diag, CFREE_DIAG_ERROR, loc, fmt, ap); - va_end(ap); -} - -static void toy_set_loc(ToyParser* p) { - if (p->cg) cfree_cg_set_loc(p->cg, p->cur.loc); -} - -static CfreeSym toy_tok_sym(ToyParser* p, ToyToken tok) { - char buf[64]; - if (tok.text_len >= sizeof(buf)) { - toy_error(p, tok.loc, "identifier too long"); - return 0; - } - memcpy(buf, tok.text, tok.text_len); - buf[tok.text_len] = '\0'; - return cfree_sym_intern(p->c, buf); -} - -/* ============================================================ - * Symbol tables - * ============================================================ */ - -static ToyVar* toy_find_var(ToyParser* p, CfreeSym name) { - size_t i; - for (i = p->nvars; i > 0; --i) { - if (p->vars[i - 1].name == name) return &p->vars[i - 1]; - } - return NULL; -} - -static ToyFn* toy_find_fn(ToyParser* p, CfreeSym name) { - size_t i; - for (i = p->nfns; i > 0; --i) { - if (p->fns[i - 1].name == name) return &p->fns[i - 1]; - } - return NULL; -} - -static ToyGlobal* toy_find_global(ToyParser* p, CfreeSym name) { - size_t i; - for (i = p->nglobals; i > 0; --i) { - if (p->globals[i - 1].name == name) return &p->globals[i - 1]; - } - return NULL; -} - -static void toy_push_var_lvalue(ToyParser* p, const ToyVar* v) { - cfree_cg_push_local(p->cg, v->local); -} - -static void toy_push_var_addr(ToyParser* p, const ToyVar* v) { - cfree_cg_push_local_addr(p->cg, v->local); -} - -static CfreeCgSym toy_find_decl_sym(ToyParser* p, CfreeSym name) { - ToyGlobal* g = toy_find_global(p, name); - if (g) return g->sym; - { - ToyFn* fn = toy_find_fn(p, name); - if (fn) return fn->sym; - } - return CFREE_CG_SYM_NONE; -} - -/* ============================================================ - * Type parsing - * ============================================================ */ - -static CfreeCgTypeId toy_parse_type(ToyParser* p) { - if (toy_parser_match(p, TOK_FN)) { - CfreeCgTypeId param_types[TOY_MAX_PARAMS]; - CfreeCgFuncParam sig_params[TOY_MAX_PARAMS]; - CfreeCgFuncSig sig; - CfreeCgTypeId ret_type; - size_t nparams = 0; - int variadic = 0; - size_t i; - - if (!toy_parser_expect(p, TOK_LPAREN)) { - toy_error(p, p->cur.loc, "expected '(' after function type"); - return CFREE_CG_TYPE_NONE; - } - if (p->cur.kind != TOK_RPAREN) { - for (;;) { - if (p->cur.kind == TOK_DOTDOTDOT) { - variadic = 1; - toy_parser_advance(p); - break; - } - if (nparams >= TOY_MAX_PARAMS) { - toy_error(p, p->cur.loc, "too many function type parameters"); - return CFREE_CG_TYPE_NONE; - } - param_types[nparams] = toy_parse_type(p); - if (param_types[nparams] == CFREE_CG_TYPE_NONE) - return CFREE_CG_TYPE_NONE; - nparams++; - if (p->cur.kind == TOK_COMMA) { - toy_parser_advance(p); - if (p->cur.kind == TOK_DOTDOTDOT) { - variadic = 1; - toy_parser_advance(p); - break; - } - } else { - break; - } - } - } - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')' after function type parameters"); - return CFREE_CG_TYPE_NONE; - } - if (!toy_parser_expect(p, TOK_COLON)) { - toy_error(p, p->cur.loc, "expected ':' before function return type"); - return CFREE_CG_TYPE_NONE; - } - ret_type = toy_parse_type(p); - if (ret_type == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - - memset(sig_params, 0, sizeof sig_params); - for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i]; - memset(&sig, 0, sizeof sig); - sig.ret = ret_type; - sig.params = sig_params; - sig.nparams = (uint32_t)nparams; - sig.call_conv = CFREE_CG_CC_TARGET_C; - sig.abi_variadic = variadic; - return cfree_cg_type_func(p->c, sig); - } - if (toy_parser_match(p, TOK_LBRACKET)) { - uint64_t count; - CfreeCgTypeId elem; - if (p->cur.kind != TOK_NUMBER || p->cur.is_float || - p->cur.int_value < 0) { - toy_error(p, p->cur.loc, "expected array count"); - return CFREE_CG_TYPE_NONE; - } - count = (uint64_t)p->cur.int_value; - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_RBRACKET)) { - toy_error(p, p->cur.loc, "expected ']' after array count"); - return CFREE_CG_TYPE_NONE; - } - elem = toy_parse_type(p); - if (elem == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - return cfree_cg_type_array(p->c, elem, count); - } - if (toy_parser_match(p, TOK_INT)) { - return p->int_type; - } - if (p->cur.kind == TOK_IDENT) { - CfreeCgTypeId ty = CFREE_CG_TYPE_NONE; - if (p->cur.text_len == 4 && memcmp(p->cur.text, "void", 4) == 0) - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - else if (p->cur.text_len == 4 && memcmp(p->cur.text, "bool", 4) == 0) - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); - else if (p->cur.text_len == 2 && p->cur.text[0] == 'i' && - p->cur.text[1] == '8') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); - else if (p->cur.text_len == 2 && p->cur.text[0] == 'u' && - p->cur.text[1] == '8') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && - p->cur.text[1] == '1' && p->cur.text[2] == '6') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I16); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && - p->cur.text[1] == '1' && p->cur.text[2] == '6') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I16); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && - p->cur.text[1] == '3' && p->cur.text[2] == '2') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I32); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && - p->cur.text[1] == '3' && p->cur.text[2] == '2') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I32); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && - p->cur.text[1] == '6' && p->cur.text[2] == '4') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); - else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && - p->cur.text[1] == '6' && p->cur.text[2] == '4') - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); - else if (p->cur.text_len == 5 && memcmp(p->cur.text, "isize", 5) == 0) - ty = p->int_type; - else if (p->cur.text_len == 5 && memcmp(p->cur.text, "usize", 5) == 0) - ty = p->int_type; - else if (p->cur.text_len == 3 && memcmp(p->cur.text, "f32", 3) == 0) - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_F32); - else if (p->cur.text_len == 3 && memcmp(p->cur.text, "f64", 3) == 0) - ty = toy_builtin_type(p, CFREE_CG_BUILTIN_F64); - else if (p->cur.text_len == 7 && memcmp(p->cur.text, "va_list", 7) == 0) - ty = p->va_list_type; - else if (p->cur.text_len == 4 && memcmp(p->cur.text, "Pair", 4) == 0) - ty = p->pair_type; - if (ty != CFREE_CG_TYPE_NONE) { - toy_parser_advance(p); - return ty; - } - } - if (toy_parser_match(p, TOK_STAR)) { - CfreeCgTypeId pointee = toy_parse_type(p); - if (pointee == CFREE_CG_TYPE_NONE) { - toy_error(p, p->cur.loc, "expected type after '*'"); - return CFREE_CG_TYPE_NONE; - } - return cfree_cg_type_ptr(p->c, pointee, 0); - } - toy_error(p, p->cur.loc, "expected type"); - return CFREE_CG_TYPE_NONE; -} - -/* ============================================================ - * Expression parsing - * ============================================================ */ - -static CfreeCgTypeId toy_parse_expr(ToyParser* p); -static CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type); - -static CfreeCgTypeId toy_ptr_pointee_func_type(ToyParser* p, - CfreeCgTypeId ptr_ty) { - CfreeCgTypeId fn_ty; - if (cfree_cg_type_kind(p->c, ptr_ty) != CFREE_CG_TYPE_PTR) - return CFREE_CG_TYPE_NONE; - fn_ty = cfree_cg_type_ptr_pointee(p->c, ptr_ty); - if (cfree_cg_type_kind(p->c, fn_ty) != CFREE_CG_TYPE_FUNC) - return CFREE_CG_TYPE_NONE; - return fn_ty; -} - -static CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) { - ToyVar* v = toy_find_var(p, name); - if (v) { - toy_push_var_lvalue(p, v); - cfree_cg_load(p->cg, toy_mem_access(p, v->type)); - return v->type; - } - { - ToyGlobal* g = toy_find_global(p, name); - if (g) { - cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); - cfree_cg_load(p->cg, toy_mem_access(p, g->type)); - return g->type; - } - } - { - ToyFn* fn = toy_find_fn(p, name); - if (fn) { - cfree_cg_push_symbol_addr(p->cg, fn->sym, 0); - return cfree_cg_type_ptr(p->c, fn->type, 0); - } - } - return CFREE_CG_TYPE_NONE; -} - -static int toy_parse_call_args(ToyParser* p, ToyToken call_tok, - CfreeCgTypeId fn_ty, size_t* out_nargs) { - uint32_t nparams = cfree_cg_type_func_nparams(p->c, fn_ty); - int variadic = cfree_cg_type_func_is_variadic(p->c, fn_ty); - size_t nargs = 0; - if (p->cur.kind != TOK_RPAREN) { - for (;;) { - CfreeCgTypeId arg_ty; - if (nargs >= TOY_MAX_PARAMS) { - toy_error(p, p->cur.loc, "too many arguments"); - return 0; - } - arg_ty = toy_parse_expr(p); - if (arg_ty == CFREE_CG_TYPE_NONE) return 0; - if (nargs < nparams) { - CfreeCgFuncParam param = - cfree_cg_type_func_param(p->c, fn_ty, (uint32_t)nargs); - if (arg_ty != param.type) { - toy_error(p, call_tok.loc, "function argument type mismatch"); - return 0; - } - } else if (!variadic) { - toy_error(p, call_tok.loc, "too many arguments"); - return 0; - } - nargs++; - if (!toy_parser_match(p, TOK_COMMA)) break; - } - } - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')' after arguments"); - return 0; - } - if (nargs < nparams) { - toy_error(p, call_tok.loc, "too few arguments"); - return 0; - } - *out_nargs = nargs; - return 1; -} - -static int toy_sym_is(ToyParser* p, CfreeSym sym, const char* name) { - return sym == cfree_sym_intern(p->c, name); -} - -static void toy_emit_int_bytes(ToyParser* p, uint64_t v, uint8_t* buf, - size_t n) { - size_t i; - (void)p; - for (i = 0; i < n; ++i) buf[i] = (uint8_t)(v >> (i * 8u)); -} - -static CfreeCgMemAccess toy_mem_access(ToyParser* p, CfreeCgTypeId type) { - CfreeCgMemAccess access; - (void)p; - memset(&access, 0, sizeof access); - access.type = type; - return access; -} - -static CfreeCgMemAccess toy_mem_access_align(ToyParser* p, CfreeCgTypeId type, - uint32_t align) { - CfreeCgMemAccess access = toy_mem_access(p, type); - access.align = align; - return access; -} - -static int toy_type_is_intlike(ToyParser* p, CfreeCgTypeId ty) { - CfreeCgTypeKind k = cfree_cg_type_kind(p->c, ty); - return k == CFREE_CG_TYPE_INT || k == CFREE_CG_TYPE_BOOL; -} - -static int toy_type_is_float(ToyParser* p, CfreeCgTypeId ty) { - return cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_FLOAT; -} - -static int toy_type_is_ptr(ToyParser* p, CfreeCgTypeId ty) { - return cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR; -} - -static uint32_t toy_type_int_width(ToyParser* p, CfreeCgTypeId ty) { - if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_BOOL) return 1; - return cfree_cg_type_int_width(p->c, ty); -} - -static int toy_emit_cast(ToyParser* p, CfreeCgTypeId src, CfreeCgTypeId dst) { - CfreeCgTypeKind sk, dk; - if (src == dst) return 1; - sk = cfree_cg_type_kind(p->c, src); - dk = cfree_cg_type_kind(p->c, dst); - - if (dk == CFREE_CG_TYPE_BOOL && toy_type_is_intlike(p, src)) { - cfree_cg_push_int(p->cg, 0, src); - cfree_cg_int_cmp(p->cg, CFREE_CG_INT_NE); - return 1; - } - if (toy_type_is_intlike(p, src) && toy_type_is_intlike(p, dst)) { - uint32_t sw = toy_type_int_width(p, src); - uint32_t dw = toy_type_int_width(p, dst); - if (dw > sw && sk == CFREE_CG_TYPE_BOOL) cfree_cg_zext(p->cg, dst); - else if (dw > sw) cfree_cg_sext(p->cg, dst); - else if (dw < sw) cfree_cg_trunc(p->cg, dst); - return 1; - } - if (toy_type_is_intlike(p, src) && dk == CFREE_CG_TYPE_FLOAT) { - cfree_cg_sint_to_float(p->cg, dst, CFREE_CG_ROUND_DEFAULT); - return 1; - } - if (sk == CFREE_CG_TYPE_FLOAT && toy_type_is_intlike(p, dst)) { - cfree_cg_float_to_sint(p->cg, dst, CFREE_CG_ROUND_TOWARD_ZERO); - return 1; - } - if (sk == CFREE_CG_TYPE_FLOAT && dk == CFREE_CG_TYPE_FLOAT) { - uint32_t sw = cfree_cg_type_float_width(p->c, src); - uint32_t dw = cfree_cg_type_float_width(p->c, dst); - if (dw > sw) cfree_cg_fpext(p->cg, dst); - else if (dw < sw) cfree_cg_fptrunc(p->cg, dst); - return 1; - } - if (sk == CFREE_CG_TYPE_PTR && toy_type_is_intlike(p, dst)) { - cfree_cg_ptr_to_int(p->cg, dst); - return 1; - } - if (toy_type_is_intlike(p, src) && dk == CFREE_CG_TYPE_PTR) { - cfree_cg_int_to_ptr(p->cg, dst); - return 1; - } - if (sk == CFREE_CG_TYPE_PTR && dk == CFREE_CG_TYPE_PTR) { - cfree_cg_bitcast(p->cg, dst); - return 1; - } - toy_error(p, p->cur.loc, "unsupported cast"); - return 0; -} - -static int toy_parse_mem_order(ToyParser* p, CfreeCgMemOrder* out) { - CfreeSym name; - if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected memory order"); - return 0; - } - name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (toy_sym_is(p, name, "relaxed")) *out = CFREE_CG_MO_RELAXED; - else if (toy_sym_is(p, name, "consume")) *out = CFREE_CG_MO_CONSUME; - else if (toy_sym_is(p, name, "acquire")) *out = CFREE_CG_MO_ACQUIRE; - else if (toy_sym_is(p, name, "release")) *out = CFREE_CG_MO_RELEASE; - else if (toy_sym_is(p, name, "acq_rel")) *out = CFREE_CG_MO_ACQ_REL; - else if (toy_sym_is(p, name, "seq_cst")) *out = CFREE_CG_MO_SEQ_CST; - else { - toy_error(p, p->cur.loc, "unknown memory order"); - return 0; - } - return 1; -} - -static int toy_parse_atomic_op(ToyParser* p, CfreeCgAtomicOp* out) { - CfreeSym name; - if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected atomic op"); - return 0; - } - name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (toy_sym_is(p, name, "xchg")) *out = CFREE_CG_ATOMIC_XCHG; - else if (toy_sym_is(p, name, "add")) *out = CFREE_CG_ATOMIC_ADD; - else if (toy_sym_is(p, name, "sub")) *out = CFREE_CG_ATOMIC_SUB; - else if (toy_sym_is(p, name, "and")) *out = CFREE_CG_ATOMIC_AND; - else if (toy_sym_is(p, name, "or")) *out = CFREE_CG_ATOMIC_OR; - else if (toy_sym_is(p, name, "xor")) *out = CFREE_CG_ATOMIC_XOR; - else if (toy_sym_is(p, name, "nand")) *out = CFREE_CG_ATOMIC_NAND; - else { - toy_error(p, p->cur.loc, "unknown atomic op"); - return 0; - } - return 1; -} - -static CfreeCgLocalAttrs toy_slot_attrs(CfreeSym name) { - CfreeCgLocalAttrs attrs; - memset(&attrs, 0, sizeof attrs); - attrs.name = name; - return attrs; -} - -static CfreeSym toy_c_linkage_name(ToyParser* p, CfreeSym source_name) { - CfreeSym linkage_name = cfree_cg_c_linkage_name(p->c, source_name); - if (!linkage_name) { - toy_error(p, p->cur.loc, "failed to create linkage name"); - } - return linkage_name; -} - -static void toy_inline_asm(ToyParser* p, CfreeSym tmpl, - const CfreeCgAsmOperand* outputs, - uint32_t noutputs, - const CfreeCgAsmOperand* inputs, uint32_t ninputs, - const CfreeSym* clobbers, uint32_t nclobbers, - uint32_t flags) { - CfreeCgInlineAsm asm_block; - memset(&asm_block, 0, sizeof asm_block); - asm_block.tmpl = tmpl; - asm_block.outputs = outputs; - asm_block.noutputs = noutputs; - asm_block.inputs = inputs; - asm_block.ninputs = ninputs; - asm_block.clobbers = clobbers; - asm_block.nclobbers = nclobbers; - asm_block.flags = flags; - cfree_cg_inline_asm(p->cg, asm_block); -} - -static int toy_emit_truthy(ToyParser* p, CfreeCgTypeId type) { - if (type == toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)) return 1; - if (toy_type_is_intlike(p, type)) { - cfree_cg_push_int(p->cg, 0, type); - cfree_cg_int_cmp(p->cg, CFREE_CG_INT_NE); - return 1; - } - toy_error(p, p->cur.loc, "condition must be int or bool"); - return 0; -} - -static int toy_expect_comma(ToyParser* p) { - if (!toy_parser_expect(p, TOK_COMMA)) { - toy_error(p, p->cur.loc, "expected ','"); - return 0; - } - return 1; -} - -static int toy_parse_number_arg(ToyParser* p, int64_t* out) { - if (p->cur.kind != TOK_NUMBER || p->cur.is_float) { - toy_error(p, p->cur.loc, "expected integer constant"); - return 0; - } - *out = p->cur.int_value; - toy_parser_advance(p); - return 1; -} - -static int toy_validate_bit_range(ToyParser* p, CfreeCgTypeId ty, int64_t lo, - int64_t width, int allow_full_width) { - uint32_t type_width; - if (!toy_type_is_intlike(p, ty) || lo < 0 || width <= 0) return 0; - type_width = toy_type_int_width(p, ty); - if (width > (int64_t)type_width) return 0; - if (!allow_full_width && width >= 64) return 0; - if (lo > (int64_t)type_width || width > (int64_t)type_width - lo) return 0; - return 1; -} - -static int toy_target_selector_index(ToyParser* p) { - switch (p->target.arch) { - case CFREE_ARCH_ARM_64: - return 0; - case CFREE_ARCH_X86_64: - return 1; - case CFREE_ARCH_RV64: - return 2; - default: - return -1; - } -} - -static int toy_target_code(ToyParser* p) { - switch (p->target.arch) { - case CFREE_ARCH_ARM_64: - return 1; - case CFREE_ARCH_X86_64: - return 2; - case CFREE_ARCH_RV64: - return 3; - default: - return 0; - } -} - -static int toy_parse_string_sym(ToyParser* p, CfreeSym* out, size_t* len_out) { - char buf[256]; - size_t len; - if (p->cur.kind != TOK_STRING || p->cur.text_len < 2) { - toy_error(p, p->cur.loc, "expected string literal"); - return 0; - } - len = p->cur.text_len - 2; - if (len >= sizeof buf) { - toy_error(p, p->cur.loc, "string literal too long"); - return 0; - } - memcpy(buf, p->cur.text + 1, len); - buf[len] = '\0'; - *out = cfree_sym_intern(p->c, buf); - if (len_out) *len_out = len; - toy_parser_advance(p); - return 1; -} - -static int toy_parse_arch_string(ToyParser* p, CfreeSym* out, - size_t* len_out) { - CfreeSym selected = 0; - size_t selected_len = 0; - int selected_index = toy_target_selector_index(p); - - if (p->cur.kind == TOK_STRING) return toy_parse_string_sym(p, out, len_out); - - if (p->cur.kind == TOK_IDENT) { - CfreeSym name = toy_tok_sym(p, p->cur); - if (toy_sym_is(p, name, "arch")) { - int i; - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_LPAREN)) { - toy_error(p, p->cur.loc, "expected '(' after arch"); - return 0; - } - for (i = 0; i < 3; ++i) { - CfreeSym s; - size_t n; - if (!toy_parse_string_sym(p, &s, &n)) return 0; - if (i == selected_index) { - selected = s; - selected_len = n; - } - if (i != 2 && !toy_expect_comma(p)) return 0; - } - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')' after arch selector"); - return 0; - } - *out = selected ? selected : cfree_sym_intern(p->c, ""); - if (len_out) *len_out = selected_len; - return 1; - } - } - - toy_error(p, p->cur.loc, "expected asm template or arch selector"); - return 0; -} - -static int toy_emit_var_addr(ToyParser* p, CfreeSym name) { - ToyVar* v = toy_find_var(p, name); - if (v) { - toy_push_var_addr(p, v); - return 1; - } - { - ToyGlobal* g = toy_find_global(p, name); - if (g) { - cfree_cg_push_symbol_addr(p->cg, g->sym, 0); - return 1; - } - } - return 0; -} - -static CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name) { - ToyVar* v = toy_find_var(p, name); - if (v) { - toy_push_var_lvalue(p, v); - return v->type; - } - { - ToyGlobal* g = toy_find_global(p, name); - if (g) { - cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); - return g->type; - } - } - return CFREE_CG_TYPE_NONE; -} - -static int toy_parse_va_list_addr_arg(ToyParser* p) { - CfreeSym name; - ToyVar* v; - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected va_list identifier"); - return 0; - } - name = toy_tok_sym(p, p->cur); - v = toy_find_var(p, name); - if (!v || v->type != p->va_list_type) { - toy_error(p, p->cur.loc, "expected va_list local"); - return 0; - } - toy_parser_advance(p); - return toy_emit_var_addr(p, name); -} - -static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, - int* recognized) { - *recognized = 1; - - if (toy_sym_is(p, name, "byteconst")) { - uint8_t buf[16]; - size_t n = (size_t)cfree_cg_type_size(p->c, p->int_type); - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected byteconst()"); - return CFREE_CG_TYPE_NONE; - } - if (n > sizeof buf) n = sizeof buf; - toy_emit_int_bytes(p, 42, buf, n); - cfree_cg_push_bytes(p->cg, buf, n, p->int_type); - cfree_cg_indirect(p->cg); - cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); - return p->int_type; - } - - if (toy_sym_is(p, name, "popcount") || toy_sym_is(p, name, "ctz") || - toy_sym_is(p, name, "clz") || toy_sym_is(p, name, "bswap")) { - CfreeCgIntrinsic intrin = CFREE_CG_INTRIN_POPCOUNT; - CfreeCgTypeId ty; - if (toy_sym_is(p, name, "ctz")) intrin = CFREE_CG_INTRIN_CTZ; - else if (toy_sym_is(p, name, "clz")) intrin = CFREE_CG_INTRIN_CLZ; - else if (toy_sym_is(p, name, "bswap")) intrin = CFREE_CG_INTRIN_BSWAP; - toy_parser_advance(p); /* ( */ - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')'"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_intrinsic(p->cg, intrin, 1, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "expect")) { - CfreeCgTypeId a, b; - toy_parser_advance(p); - a = toy_parse_expr(p); - if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - b = toy_parse_expr(p); - if (b == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_EXPECT, 2, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "trap")) { - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_intrinsic(p->cg, CFREE_CG_INTRIN_TRAP, 0, - toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)); - return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - } - - if (toy_sym_is(p, name, "unreachable")) { - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_unreachable(p->cg); - return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - } - - if (toy_sym_is(p, name, "bitget")) { - CfreeCgTypeId ty; - int64_t lo, width; - toy_parser_advance(p); - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &lo) || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &width) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (!toy_validate_bit_range(p, ty, lo, width, 1)) { - toy_error(p, p->cur.loc, "invalid bitget arguments"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_bitget(p->cg, ty, (uint32_t)lo, (uint32_t)width); - return ty; - } - - if (toy_sym_is(p, name, "bitset")) { - CfreeCgTypeId dst_ty, src_ty; - int64_t lo, width; - CfreeCgLocal dst_slot, src_slot; - uint64_t src_mask, field_mask, clear_mask; - toy_parser_advance(p); - dst_ty = toy_parse_expr(p); - if (dst_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - src_ty = toy_parse_expr(p); - if (src_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &lo) || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &width) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (dst_ty != src_ty || !toy_validate_bit_range(p, dst_ty, lo, width, 0)) { - toy_error(p, p->cur.loc, "invalid bitset arguments"); - return CFREE_CG_TYPE_NONE; - } - src_mask = (1ULL << (uint32_t)width) - 1u; - field_mask = src_mask << (uint32_t)lo; - clear_mask = ~field_mask; - src_slot = cfree_cg_local(p->cg, src_ty, toy_slot_attrs(0)); - dst_slot = cfree_cg_local(p->cg, dst_ty, toy_slot_attrs(0)); - cfree_cg_push_local(p->cg, src_slot); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, src_ty)); - cfree_cg_push_local(p->cg, dst_slot); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, dst_ty)); - - cfree_cg_push_local(p->cg, dst_slot); - cfree_cg_load(p->cg, toy_mem_access(p, dst_ty)); - cfree_cg_push_int(p->cg, clear_mask, dst_ty); - cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); - cfree_cg_push_local(p->cg, src_slot); - cfree_cg_load(p->cg, toy_mem_access(p, src_ty)); - cfree_cg_push_int(p->cg, src_mask, src_ty); - cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); - if (lo > 0) { - cfree_cg_push_int(p->cg, (uint64_t)lo, src_ty); - cfree_cg_int_binop(p->cg, CFREE_CG_INT_SHL, 0); - } - cfree_cg_int_binop(p->cg, CFREE_CG_INT_OR, 0); - return dst_ty; - } - - if (toy_sym_is(p, name, "index")) { - CfreeCgTypeId base, idx; - toy_parser_advance(p); - base = toy_parse_expr(p); - if (base == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - idx = toy_parse_expr(p); - if (idx == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (base != p->int_ptr_type || idx != p->int_type) { - toy_error(p, p->cur.loc, "index expects (*int, int)"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_index(p->cg, 0); - cfree_cg_addr(p->cg); - return p->int_ptr_type; - } - - if (toy_sym_is(p, name, "memset")) { - CfreeCgTypeId dst; - int64_t val, size, align = 0; - int has_align = 0; - toy_parser_advance(p); - dst = toy_parse_expr(p); - if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &val) || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; - if (toy_parser_match(p, TOK_COMMA)) { - has_align = 1; - if (!toy_parse_number_arg(p, &align)) return CFREE_CG_TYPE_NONE; - } - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_memset(p->cg, (uint8_t)val, (uint64_t)size, - toy_mem_access_align(p, p->int_type, (uint32_t)align)); - if (has_align) return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "memcpy")) { - CfreeCgTypeId dst, src; - int64_t size, align = 0; - int has_align = 0; - toy_parser_advance(p); - dst = toy_parse_expr(p); - if (dst == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - src = toy_parse_expr(p); - if (src == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_number_arg(p, &size)) return CFREE_CG_TYPE_NONE; - if (toy_parser_match(p, TOK_COMMA)) { - has_align = 1; - if (!toy_parse_number_arg(p, &align)) return CFREE_CG_TYPE_NONE; - } - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_memcpy(p->cg, (uint64_t)size, - toy_mem_access_align(p, p->int_type, (uint32_t)align), - toy_mem_access_align(p, p->int_type, (uint32_t)align)); - if (has_align) return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "atomic_load")) { - CfreeCgTypeId ty; - toy_parser_advance(p); - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_atomic_load(p->cg, toy_mem_access(p, p->int_type), - CFREE_CG_MO_SEQ_CST); - return p->int_type; - } - - if (toy_sym_is(p, name, "atomic_store") || - toy_sym_is(p, name, "atomic_add") || - toy_sym_is(p, name, "atomic_sub")) { - CfreeCgTypeId ptr_ty, val_ty; - int is_store = toy_sym_is(p, name, "atomic_store"); - CfreeCgAtomicOp op = toy_sym_is(p, name, "atomic_sub") - ? CFREE_CG_ATOMIC_SUB - : CFREE_CG_ATOMIC_ADD; - toy_parser_advance(p); - ptr_ty = toy_parse_expr(p); - if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - val_ty = toy_parse_expr(p); - if (val_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (is_store) { - cfree_cg_atomic_store(p->cg, toy_mem_access(p, p->int_type), - CFREE_CG_MO_SEQ_CST); - cfree_cg_push_int(p->cg, 0, p->int_type); - } else { - cfree_cg_atomic_rmw(p->cg, toy_mem_access(p, p->int_type), op, - CFREE_CG_MO_SEQ_CST); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "atomic_cas_ok")) { - CfreeCgTypeId t0, t1, t2; - toy_parser_advance(p); - t0 = toy_parse_expr(p); - if (t0 == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - t1 = toy_parse_expr(p); - if (t1 == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - t2 = toy_parse_expr(p); - if (t2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_atomic_cmpxchg(p->cg, toy_mem_access(p, p->int_type), - CFREE_CG_MO_SEQ_CST, CFREE_CG_MO_RELAXED, 0); - cfree_cg_swap(p->cg); - cfree_cg_drop(p->cg); - return p->int_type; - } - - if (toy_sym_is(p, name, "fence")) { - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_atomic_fence(p->cg, CFREE_CG_MO_SEQ_CST); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "atomic_fence")) { - CfreeCgMemOrder order; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_mem_order(p, &order) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_atomic_fence(p->cg, order); - return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - } - - if (toy_sym_is(p, name, "target")) { - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_push_int(p->cg, (uint64_t)toy_target_code(p), p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "target_os")) { - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_push_int(p->cg, (uint64_t)p->target.os, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "va_start")) { - if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_vararg_start(p->cg); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "va_end")) { - if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_vararg_end(p->cg); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "va_copy")) { - if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || - !toy_expect_comma(p) || !toy_parse_va_list_addr_arg(p) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_vararg_copy(p->cg); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "va_arg")) { - CfreeCgTypeId ty; - if (!toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_vararg_next(p->cg, ty); - return ty; - } - - if (toy_sym_is(p, name, "fieldtest")) { - uint64_t sz = cfree_cg_type_size(p->c, p->pair_type); - CfreeCgLocal slot; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (sz == 0) { - toy_error(p, p->cur.loc, "invalid Pair layout"); - return CFREE_CG_TYPE_NONE; - } - slot = cfree_cg_local(p->cg, p->pair_type, toy_slot_attrs(0)); - cfree_cg_push_local(p->cg, slot); - cfree_cg_addr(p->cg); - cfree_cg_dup(p->cg); - cfree_cg_indirect(p->cg); - cfree_cg_field(p->cg, 1); - cfree_cg_push_int(p->cg, 42, p->int_type); - cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); - cfree_cg_indirect(p->cg); - cfree_cg_field(p->cg, 1); - cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); - return p->int_type; - } - - if (toy_sym_is(p, name, "asmnop")) { - CfreeSym tmpl = cfree_sym_intern(p->c, "nop"); - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - toy_inline_asm(p, tmpl, NULL, 0, NULL, 0, NULL, 0, - CFREE_CG_ASM_VOLATILE); - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "asm")) { - CfreeSym tmpl; - size_t tmpl_len; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (tmpl_len) { - toy_inline_asm(p, tmpl, NULL, 0, NULL, 0, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } - cfree_cg_push_int(p->cg, 0, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_int")) { - CfreeSym tmpl; - size_t tmpl_len; - CfreeCgTypeId a, b; - CfreeCgAsmOperand outputs[1]; - CfreeCgAsmOperand inputs[2]; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - a = toy_parse_expr(p); - if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - b = toy_parse_expr(p); - if (b == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (a != p->int_type || b != p->int_type) { - toy_error(p, p->cur.loc, "asm_int expects int inputs"); - return CFREE_CG_TYPE_NONE; - } - if (tmpl_len) { - memset(outputs, 0, sizeof outputs); - memset(inputs, 0, sizeof inputs); - outputs[0].constraint = cfree_sym_intern(p->c, "=r"); - outputs[0].type = p->int_type; - outputs[0].dir = CFREE_CG_ASM_OUT; - inputs[0].constraint = cfree_sym_intern(p->c, "r"); - inputs[0].type = p->int_type; - inputs[0].dir = CFREE_CG_ASM_IN; - inputs[1] = inputs[0]; - toy_inline_asm(p, tmpl, outputs, 1, inputs, 2, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } else { - cfree_cg_drop(p->cg); - cfree_cg_drop(p->cg); - cfree_cg_push_int(p->cg, 0, p->int_type); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_imm")) { - CfreeSym tmpl; - size_t tmpl_len; - int64_t imm; - CfreeCgAsmOperand outputs[1]; - CfreeCgAsmOperand inputs[1]; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p) || !toy_parse_number_arg(p, &imm) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (tmpl_len) { - memset(outputs, 0, sizeof outputs); - memset(inputs, 0, sizeof inputs); - outputs[0].constraint = cfree_sym_intern(p->c, "=r"); - outputs[0].type = p->int_type; - outputs[0].dir = CFREE_CG_ASM_OUT; - inputs[0].constraint = cfree_sym_intern(p->c, "i"); - inputs[0].type = p->int_type; - inputs[0].dir = CFREE_CG_ASM_IN; - cfree_cg_push_int(p->cg, (uint64_t)imm, p->int_type); - toy_inline_asm(p, tmpl, outputs, 1, inputs, 1, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } else { - cfree_cg_push_int(p->cg, 0, p->int_type); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_mem")) { - CfreeSym tmpl; - size_t tmpl_len; - CfreeSym var_name; - CfreeCgTypeId var_ty; - CfreeCgAsmOperand outputs[1]; - CfreeCgAsmOperand inputs[1]; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "asm_mem expects an identifier"); - return CFREE_CG_TYPE_NONE; - } - var_name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - var_ty = toy_emit_var_lvalue(p, var_name); - if (var_ty != p->int_type) { - toy_error(p, p->cur.loc, "asm_mem expects an int lvalue"); - return CFREE_CG_TYPE_NONE; - } - if (tmpl_len) { - memset(outputs, 0, sizeof outputs); - memset(inputs, 0, sizeof inputs); - outputs[0].constraint = cfree_sym_intern(p->c, "=r"); - outputs[0].type = p->int_type; - outputs[0].dir = CFREE_CG_ASM_OUT; - inputs[0].constraint = cfree_sym_intern(p->c, "m"); - inputs[0].type = p->int_type; - inputs[0].dir = CFREE_CG_ASM_IN; - toy_inline_asm(p, tmpl, outputs, 1, inputs, 1, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } else { - cfree_cg_load(p->cg, toy_mem_access(p, var_ty)); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_inout")) { - CfreeSym tmpl; - size_t tmpl_len; - CfreeCgTypeId ty; - CfreeCgAsmOperand outputs[1]; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type) { - toy_error(p, p->cur.loc, "asm_inout expects an int input"); - return CFREE_CG_TYPE_NONE; - } - if (tmpl_len) { - memset(outputs, 0, sizeof outputs); - outputs[0].constraint = cfree_sym_intern(p->c, "+r"); - outputs[0].type = p->int_type; - outputs[0].dir = CFREE_CG_ASM_INOUT; - toy_inline_asm(p, tmpl, outputs, 1, NULL, 0, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_early")) { - CfreeSym tmpl; - size_t tmpl_len; - CfreeCgTypeId a, b; - CfreeCgAsmOperand outputs[1]; - CfreeCgAsmOperand inputs[2]; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - a = toy_parse_expr(p); - if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - b = toy_parse_expr(p); - if (b == CFREE_CG_TYPE_NONE || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (a != p->int_type || b != p->int_type) { - toy_error(p, p->cur.loc, "asm_early expects int inputs"); - return CFREE_CG_TYPE_NONE; - } - if (tmpl_len) { - memset(outputs, 0, sizeof outputs); - memset(inputs, 0, sizeof inputs); - outputs[0].constraint = cfree_sym_intern(p->c, "=&r"); - outputs[0].type = p->int_type; - outputs[0].dir = CFREE_CG_ASM_OUT; - inputs[0].constraint = cfree_sym_intern(p->c, "r"); - inputs[0].type = p->int_type; - inputs[0].dir = CFREE_CG_ASM_IN; - inputs[1] = inputs[0]; - toy_inline_asm(p, tmpl, outputs, 1, inputs, 2, NULL, 0, - CFREE_CG_ASM_VOLATILE); - } else { - cfree_cg_int_binop(p->cg, CFREE_CG_INT_ADD, 0); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_memory")) { - CfreeSym tmpl; - CfreeSym clobber; - size_t tmpl_len; - CfreeCgTypeId ty; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type) { - toy_error(p, p->cur.loc, "asm_memory expects an int input"); - return CFREE_CG_TYPE_NONE; - } - if (tmpl_len) { - clobber = cfree_sym_intern(p->c, "memory"); - toy_inline_asm(p, tmpl, NULL, 0, NULL, 0, &clobber, 1, - CFREE_CG_ASM_VOLATILE); - } - return p->int_type; - } - - if (toy_sym_is(p, name, "asm_clobber")) { - CfreeSym tmpl; - CfreeSym clobber; - size_t tmpl_len; - size_t clobber_len; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parse_arch_string(p, &tmpl, &tmpl_len) || - !toy_expect_comma(p) || - !toy_parse_arch_string(p, &clobber, &clobber_len) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (tmpl_len) { - toy_inline_asm(p, tmpl, NULL, 0, NULL, 0, - clobber_len ? &clobber : NULL, clobber_len ? 1u : 0u, - CFREE_CG_ASM_VOLATILE); - } - cfree_cg_push_int(p->cg, 11, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "typecheck")) { - CfreeCgTypeId arr, alias, enm, fnty; - CfreeCgEnumValue ev; - CfreeCgFuncParam fn_params[1]; - CfreeCgFuncSig sig; - CfreeCgField field; - CfreeCgFuncParam first_param; - int ok = 1; - if (!toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - arr = cfree_cg_type_array(p->c, p->int_type, 4); - alias = cfree_cg_type_alias(p->c, cfree_sym_intern(p->c, "Word"), p->int_type); - ev.name = cfree_sym_intern(p->c, "E"); - ev.value = 7; - enm = cfree_cg_type_enum(p->c, cfree_sym_intern(p->c, "Enum"), - p->int_type, &ev, 1); - memset(fn_params, 0, sizeof fn_params); - memset(&sig, 0, sizeof sig); - fn_params[0].type = p->int_type; - sig.ret = p->int_type; - sig.params = fn_params; - sig.nparams = 1; - sig.call_conv = CFREE_CG_CC_TARGET_C; - fnty = cfree_cg_type_func(p->c, sig); - first_param = cfree_cg_type_func_param(p->c, fnty, 0); - ok = ok && arr && alias && enm && fnty; - ok = ok && cfree_cg_type_kind(p->c, p->int_ptr_type) == CFREE_CG_TYPE_PTR; - ok = ok && cfree_cg_type_ptr_pointee(p->c, p->int_ptr_type) == p->int_type; - ok = ok && cfree_cg_type_kind(p->c, p->pair_type) == CFREE_CG_TYPE_RECORD; - ok = ok && cfree_cg_type_record_nfields(p->c, p->pair_type) == 2; - ok = ok && - cfree_cg_type_record_field(p->c, p->pair_type, 1, &field, NULL) == 0; - ok = ok && field.type == p->int_type; - ok = ok && cfree_cg_type_kind(p->c, fnty) == CFREE_CG_TYPE_FUNC; - ok = ok && cfree_cg_type_func_ret(p->c, fnty) == p->int_type; - ok = ok && cfree_cg_type_func_nparams(p->c, fnty) == 1; - ok = ok && first_param.type == p->int_type; - ok = ok && cfree_cg_type_size(p->c, arr) >= 4; - ok = ok && cfree_cg_type_align(p->c, p->int_type) >= 1; - cfree_cg_push_int(p->cg, ok ? 1 : 0, p->int_type); - return p->int_type; - } - - *recognized = 0; - return CFREE_CG_TYPE_NONE; -} - -static CfreeCgTypeId toy_parse_generic_builtin(ToyParser* p, CfreeSym name, - int* recognized) { - CfreeCgTypeId ty; - *recognized = 1; - - if (toy_sym_is(p, name, "sizeof") || toy_sym_is(p, name, "alignof")) { - int is_align = toy_sym_is(p, name, "alignof"); - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN) || - !toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected type query form"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_push_int(p->cg, - is_align ? cfree_cg_type_align(p->c, ty) - : cfree_cg_type_size(p->c, ty), - p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "offsetof")) { - uint32_t i, nfields; - uint64_t off = 0; - int found = 0; - CfreeSym field_name; - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN) || p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected offsetof<T>(field)"); - return CFREE_CG_TYPE_NONE; - } - field_name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD) { - toy_error(p, p->cur.loc, "offsetof expects a record type"); - return CFREE_CG_TYPE_NONE; - } - nfields = cfree_cg_type_record_nfields(p->c, ty); - for (i = 0; i < nfields; ++i) { - CfreeCgField field; - uint64_t field_off = 0; - if (cfree_cg_type_record_field(p->c, ty, i, &field, &field_off) == 0 && - field.name == field_name) { - off = field_off; - found = 1; - break; - } - } - if (!found) { - toy_error(p, p->cur.loc, "unknown record field"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_push_int(p->cg, off, p->int_type); - return p->int_type; - } - - if (toy_sym_is(p, name, "va_arg")) { - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN) || !toy_parse_va_list_addr_arg(p) || - !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; - cfree_cg_vararg_next(p->cg, ty); - return ty; - } - - if (toy_sym_is(p, name, "atomic_load")) { - CfreeCgTypeId ptr_ty; - CfreeCgMemOrder order; - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; - ptr_ty = toy_parse_expr(p); - if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_mem_order(p, &order) || !toy_parser_expect(p, TOK_RPAREN)) - return CFREE_CG_TYPE_NONE; - if (!toy_type_is_ptr(p, ptr_ty) || - cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty) { - toy_error(p, p->cur.loc, "atomic_load pointer type mismatch"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_atomic_load(p->cg, toy_mem_access(p, ty), order); - return ty; - } - - if (toy_sym_is(p, name, "atomic_store")) { - CfreeCgTypeId ptr_ty, val_ty; - CfreeCgMemOrder order; - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN)) return CFREE_CG_TYPE_NONE; - ptr_ty = toy_parse_expr(p); - if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - val_ty = toy_parse_expr(p); - if (val_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_mem_order(p, &order) || !toy_parser_expect(p, TOK_RPAREN)) - return CFREE_CG_TYPE_NONE; - if (!toy_type_is_ptr(p, ptr_ty) || - cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty || val_ty != ty) { - toy_error(p, p->cur.loc, "atomic_store type mismatch"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_atomic_store(p->cg, toy_mem_access(p, ty), order); - return toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - } - - if (toy_sym_is(p, name, "atomic_rmw")) { - CfreeCgAtomicOp op; - CfreeCgTypeId ptr_ty, val_ty; - CfreeCgMemOrder order; - if (!toy_parser_expect(p, TOK_LT)) return CFREE_CG_TYPE_NONE; - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE || !toy_parser_expect(p, TOK_GT) || - !toy_parser_expect(p, TOK_LPAREN) || !toy_parse_atomic_op(p, &op) || - !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - ptr_ty = toy_parse_expr(p); - if (ptr_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; - val_ty = toy_parse_expr(p); - if (val_ty == CFREE_CG_TYPE_NONE || !toy_expect_comma(p) || - !toy_parse_mem_order(p, &order) || !toy_parser_expect(p, TOK_RPAREN)) - return CFREE_CG_TYPE_NONE; - if (!toy_type_is_ptr(p, ptr_ty) || - cfree_cg_type_ptr_pointee(p->c, ptr_ty) != ty || val_ty != ty) { - toy_error(p, p->cur.loc, "atomic_rmw type mismatch"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_atomic_rmw(p->cg, toy_mem_access(p, ty), op, order); - return ty; - } - - *recognized = 0; - return CFREE_CG_TYPE_NONE; -} - -static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { - toy_set_loc(p); - if (p->cur.kind == TOK_NUMBER) { - if (p->cur.is_float) { - cfree_cg_push_float(p->cg, p->cur.float_value, - toy_builtin_type(p, CFREE_CG_BUILTIN_F64)); - toy_parser_advance(p); - return toy_builtin_type(p, CFREE_CG_BUILTIN_F64); - } - cfree_cg_push_int(p->cg, p->cur.int_value, p->int_type); - toy_parser_advance(p); - return p->int_type; - } - - if (p->cur.kind == TOK_IDENT) { - CfreeSym name = toy_tok_sym(p, p->cur); - ToyToken ident_tok = p->cur; - toy_parser_advance(p); - - if (toy_sym_is(p, name, "true") || toy_sym_is(p, name, "false")) { - cfree_cg_push_int(p->cg, toy_sym_is(p, name, "true") ? 1u : 0u, - toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL)); - return toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); - } - - if (p->cur.kind == TOK_LT) { - int recognized = 0; - CfreeCgTypeId builtin_ty = - toy_parse_generic_builtin(p, name, &recognized); - if (recognized) return builtin_ty; - } - - if (p->cur.kind == TOK_LPAREN) { - /* Function call */ - int recognized = 0; - CfreeCgTypeId builtin_ty = toy_parse_builtin_call(p, name, &recognized); - if (recognized) return builtin_ty; - - ToyFn* fn = toy_find_fn(p, name); - toy_parser_advance(p); /* ( */ - - size_t nargs = 0; - if (fn) { - if (!toy_parse_call_args(p, ident_tok, fn->type, &nargs)) - return CFREE_CG_TYPE_NONE; - cfree_cg_call_symbol_default(p->cg, fn->sym, (uint32_t)nargs); - return fn->ret; - } - - CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name); - if (callee_ty == CFREE_CG_TYPE_NONE) { - toy_error(p, ident_tok.loc, "undefined function '%s'", - (const char*)ident_tok.text); - return CFREE_CG_TYPE_NONE; - } - CfreeCgTypeId fn_ty = toy_ptr_pointee_func_type(p, callee_ty); - if (fn_ty == CFREE_CG_TYPE_NONE) { - toy_error(p, ident_tok.loc, "callee is not a function pointer"); - return CFREE_CG_TYPE_NONE; - } - if (!toy_parse_call_args(p, ident_tok, fn_ty, &nargs)) - return CFREE_CG_TYPE_NONE; - cfree_cg_call_default(p->cg, (uint32_t)nargs, fn_ty); - return cfree_cg_type_func_ret(p->c, fn_ty); - } - - { - CfreeCgTypeId ty = toy_push_named_rvalue(p, name); - if (ty != CFREE_CG_TYPE_NONE) return ty; - } - toy_error(p, ident_tok.loc, "undefined variable '%s'", - (const char*)ident_tok.text); - return CFREE_CG_TYPE_NONE; - } - - if (p->cur.kind == TOK_LPAREN) { - toy_parser_advance(p); - CfreeCgTypeId ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')'"); - return CFREE_CG_TYPE_NONE; - } - return ty; - } - - toy_error(p, p->cur.loc, "expected expression"); - return CFREE_CG_TYPE_NONE; -} - -static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { - toy_set_loc(p); - if (toy_parser_match(p, TOK_MINUS)) { - CfreeCgTypeId ty = toy_parse_expr_unary(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (toy_type_is_float(p, ty)) { - cfree_cg_fp_unop(p->cg, CFREE_CG_FP_NEG, 0); - return ty; - } - if (!toy_type_is_intlike(p, ty)) { - toy_error(p, p->cur.loc, "invalid operand for unary '-'"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_unop(p->cg, CFREE_CG_INT_NEG, 0); - return ty; - } - - if (toy_parser_match(p, TOK_BANG)) { - CfreeCgTypeId ty = toy_parse_expr_unary(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_type_is_intlike(p, ty)) { - toy_error(p, p->cur.loc, "invalid operand for '!'"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_unop(p->cg, CFREE_CG_INT_NOT, 0); - return ty; - } - - if (toy_parser_match(p, TOK_TILDE)) { - CfreeCgTypeId ty = toy_parse_expr_unary(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_type_is_intlike(p, ty)) { - toy_error(p, p->cur.loc, "invalid operand for '~'"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_unop(p->cg, CFREE_CG_INT_BNOT, 0); - return ty; - } - - if (toy_parser_match(p, TOK_AMPERSAND)) { - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected identifier after '&'"); - return CFREE_CG_TYPE_NONE; - } - CfreeSym name = toy_tok_sym(p, p->cur); - ToyVar* v = toy_find_var(p, name); - toy_parser_advance(p); - if (v) { - toy_push_var_addr(p, v); - return cfree_cg_type_ptr(p->c, v->type, 0); - } else { - ToyGlobal* g = toy_find_global(p, name); - if (g) { - cfree_cg_push_symbol_addr(p->cg, g->sym, 0); - return cfree_cg_type_ptr(p->c, g->type, 0); - } else { - ToyFn* fn = toy_find_fn(p, name); - if (fn) { - cfree_cg_push_symbol_addr(p->cg, fn->sym, 0); - return cfree_cg_type_ptr(p->c, fn->type, 0); - } - } - toy_error(p, p->cur.loc, "undefined variable"); - return CFREE_CG_TYPE_NONE; - } - } - - if (toy_parser_match(p, TOK_STAR)) { - CfreeCgTypeId ty = toy_parse_expr_unary(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != p->int_ptr_type) { - toy_error(p, p->cur.loc, "cannot dereference non-pointer"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_indirect(p->cg); - cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); - return p->int_type; - } - - return toy_parse_expr_primary(p); -} - -static CfreeCgTypeId toy_parse_expr_mul(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_unary(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_STAR || p->cur.kind == TOK_SLASH || - p->cur.kind == TOK_PERCENT) { - ToyTokenKind op = p->cur.kind; - CfreeCgIntBinOp binop; - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_unary(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { - toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); - return CFREE_CG_TYPE_NONE; - } - if (toy_type_is_float(p, ty)) { - CfreeCgFpBinOp fp_op; - if (op == TOK_PERCENT) { - toy_error(p, p->cur.loc, "floating-point remainder is unsupported"); - return CFREE_CG_TYPE_NONE; - } - switch (op) { - case TOK_STAR: - fp_op = CFREE_CG_FP_MUL; - break; - case TOK_SLASH: - fp_op = CFREE_CG_FP_DIV; - break; - case TOK_PERCENT: - fp_op = CFREE_CG_FP_REM; - break; - default: - return CFREE_CG_TYPE_NONE; - } - cfree_cg_fp_binop(p->cg, fp_op, 0); - } else { - switch (op) { - case TOK_STAR: - binop = CFREE_CG_INT_MUL; - break; - case TOK_SLASH: - binop = CFREE_CG_INT_SDIV; - break; - case TOK_PERCENT: - binop = CFREE_CG_INT_SREM; - break; - default: - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_binop(p->cg, binop, 0); - } - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_add(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_mul(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_PLUS || p->cur.kind == TOK_MINUS) { - ToyTokenKind op = p->cur.kind; - CfreeCgIntBinOp binop = - (op == TOK_PLUS) ? CFREE_CG_INT_ADD : CFREE_CG_INT_SUB; - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_mul(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != ty2 || (!toy_type_is_intlike(p, ty) && !toy_type_is_float(p, ty))) { - toy_error(p, p->cur.loc, "arithmetic operands must have same numeric type"); - return CFREE_CG_TYPE_NONE; - } - if (toy_type_is_float(p, ty)) { - cfree_cg_fp_binop(p->cg, - op == TOK_PLUS ? CFREE_CG_FP_ADD : CFREE_CG_FP_SUB, - 0); - } else { - cfree_cg_int_binop(p->cg, binop, 0); - } - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_cmp(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_add(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (p->cur.kind == TOK_EQEQ || p->cur.kind == TOK_NE || - p->cur.kind == TOK_LT || p->cur.kind == TOK_GT || - p->cur.kind == TOK_LE || p->cur.kind == TOK_GE) { - ToyTokenKind op = p->cur.kind; - CfreeCgIntCmpOp cmp; - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_add(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != ty2) { - toy_error(p, p->cur.loc, "comparison operands must have same type"); - return CFREE_CG_TYPE_NONE; - } - if (toy_type_is_float(p, ty)) { - CfreeCgFpCmpOp fp_cmp; - switch (op) { - case TOK_EQEQ: - fp_cmp = CFREE_CG_FP_OEQ; - break; - case TOK_NE: - fp_cmp = CFREE_CG_FP_ONE; - break; - case TOK_LT: - fp_cmp = CFREE_CG_FP_OLT; - break; - case TOK_GT: - fp_cmp = CFREE_CG_FP_OGT; - break; - case TOK_LE: - fp_cmp = CFREE_CG_FP_OLE; - break; - case TOK_GE: - fp_cmp = CFREE_CG_FP_OGE; - break; - default: - return CFREE_CG_TYPE_NONE; - } - cfree_cg_fp_cmp(p->cg, fp_cmp); - } else if (toy_type_is_intlike(p, ty) || toy_type_is_ptr(p, ty)) { - switch (op) { - case TOK_EQEQ: - cmp = CFREE_CG_INT_EQ; - break; - case TOK_NE: - cmp = CFREE_CG_INT_NE; - break; - case TOK_LT: - cmp = CFREE_CG_INT_LT_S; - break; - case TOK_GT: - cmp = CFREE_CG_INT_GT_S; - break; - case TOK_LE: - cmp = CFREE_CG_INT_LE_S; - break; - case TOK_GE: - cmp = CFREE_CG_INT_GE_S; - break; - default: - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_cmp(p->cg, cmp); - } else { - toy_error(p, p->cur.loc, "comparison operands must be scalar"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_zext(p->cg, p->int_type); - ty = p->int_type; - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_shift(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_cmp(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_SHL || p->cur.kind == TOK_SHR) { - ToyTokenKind op = p->cur.kind; - CfreeCgIntBinOp binop = - (op == TOK_SHL) ? CFREE_CG_INT_SHL : CFREE_CG_INT_ASHR; - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_cmp(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type || ty2 != p->int_type) { - toy_error(p, p->cur.loc, "shift operands must be int"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_binop(p->cg, binop, 0); - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_band(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_shift(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_AMPERSAND) { - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_shift(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type || ty2 != p->int_type) { - toy_error(p, p->cur.loc, "bitwise operands must be int"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_binop(p->cg, CFREE_CG_INT_AND, 0); - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_bxor(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_band(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_CARET) { - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_band(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type || ty2 != p->int_type) { - toy_error(p, p->cur.loc, "bitwise operands must be int"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_binop(p->cg, CFREE_CG_INT_XOR, 0); - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_bor(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_bxor(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_PIPE) { - toy_parser_advance(p); - CfreeCgTypeId ty2 = toy_parse_expr_bxor(p); - if (ty2 == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (ty != p->int_type || ty2 != p->int_type) { - toy_error(p, p->cur.loc, "bitwise operands must be int"); - return CFREE_CG_TYPE_NONE; - } - cfree_cg_int_binop(p->cg, CFREE_CG_INT_OR, 0); - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_and(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_bor(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_ANDAND) { - CfreeCgLabel false_label; - CfreeCgLabel end_label; - CfreeCgLocal result_slot; - toy_parser_advance(p); - false_label = cfree_cg_label_new(p->cg); - end_label = cfree_cg_label_new(p->cg); - result_slot = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); - if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; - cfree_cg_branch_false(p->cg, false_label); - ty = toy_parse_expr_bor(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; - cfree_cg_branch_false(p->cg, false_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_push_int(p->cg, 1, p->int_type); - cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); - cfree_cg_jump(p->cg, end_label); - cfree_cg_label_place(p->cg, false_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_push_int(p->cg, 0, p->int_type); - cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); - cfree_cg_label_place(p->cg, end_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); - ty = p->int_type; - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr_or(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_and(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - while (p->cur.kind == TOK_PIPEPIPE) { - CfreeCgLabel true_label; - CfreeCgLabel end_label; - CfreeCgLocal result_slot; - toy_parser_advance(p); - true_label = cfree_cg_label_new(p->cg); - end_label = cfree_cg_label_new(p->cg); - result_slot = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); - if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; - cfree_cg_branch_true(p->cg, true_label); - ty = toy_parse_expr_and(p); - if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_emit_truthy(p, ty)) return CFREE_CG_TYPE_NONE; - cfree_cg_branch_true(p->cg, true_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_push_int(p->cg, 0, p->int_type); - cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); - cfree_cg_jump(p->cg, end_label); - cfree_cg_label_place(p->cg, true_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_push_int(p->cg, 1, p->int_type); - cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); - cfree_cg_label_place(p->cg, end_label); - cfree_cg_push_local(p->cg, result_slot); - cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); - ty = p->int_type; - } - return ty; -} - -static CfreeCgTypeId toy_parse_expr(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr_or(p); - while (ty != CFREE_CG_TYPE_NONE && toy_parser_match(p, TOK_AS)) { - CfreeCgTypeId dst = toy_parse_type(p); - if (dst == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; - if (!toy_emit_cast(p, ty, dst)) return CFREE_CG_TYPE_NONE; - ty = dst; - } - return ty; -} - -/* ============================================================ - * Statement parsing - * ============================================================ */ - -static int toy_parse_stmt(ToyParser* p); - -static int toy_parse_block(ToyParser* p) { - if (!toy_parser_expect(p, TOK_LBRACE)) { - toy_error(p, p->cur.loc, "expected '{'"); - return 0; - } - while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { - if (!toy_parse_stmt(p)) return 0; - } - if (!toy_parser_expect(p, TOK_RBRACE)) { - toy_error(p, p->cur.loc, "expected '}'"); - return 0; - } - return 1; -} - -static int toy_parse_let_stmt(ToyParser* p) { - CfreeSym name; - CfreeCgTypeId ty; - CfreeCgLocal slot; - toy_parser_advance(p); /* let */ - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected identifier after 'let'"); - return 0; - } - name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_COLON)) { - toy_error(p, p->cur.loc, "expected ':' after identifier"); - return 0; - } - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE) return 0; - slot = cfree_cg_local(p->cg, ty, toy_slot_attrs(name)); - if (p->nvars >= TOY_MAX_VARS) { - toy_error(p, p->cur.loc, "too many locals"); - return 0; - } - p->vars[p->nvars].name = name; - p->vars[p->nvars].type = ty; - p->vars[p->nvars].local = slot; - p->nvars++; - - if (toy_parser_match(p, TOK_EQ)) { - CfreeCgTypeId expr_ty = toy_parse_expr(p); - if (expr_ty == CFREE_CG_TYPE_NONE) return 0; - if (expr_ty != ty) { - toy_error(p, p->cur.loc, "type mismatch in let initializer"); - return 0; - } - cfree_cg_push_local(p->cg, slot); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, ty)); - } - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after let"); - return 0; - } - return 1; -} - -static int toy_parse_if_stmt(ToyParser* p) { - CfreeCgIf it; - CfreeCgTypeId cond_ty; - toy_parser_advance(p); /* if */ - cond_ty = toy_parse_expr(p); - if (cond_ty == CFREE_CG_TYPE_NONE) return 0; - if (!toy_emit_truthy(p, cond_ty)) return 0; - - it = cfree_cg_if_begin(p->cg); - - if (!toy_parse_block(p)) return 0; - - cfree_cg_if_else(p->cg, it); - - if (p->cur.kind == TOK_ELSE) { - toy_parser_advance(p); /* else */ - if (p->cur.kind == TOK_LBRACE) { - if (!toy_parse_block(p)) return 0; - } else { - if (!toy_parse_stmt(p)) return 0; - } - } - - cfree_cg_if_end(p->cg, it); - return 1; -} - -static int toy_parse_while_stmt(ToyParser* p) { - CfreeCgScope scope; - CfreeCgTypeId cond_ty; - - toy_parser_advance(p); /* while */ - - if (p->nscopes >= TOY_MAX_SCOPES) { - toy_error(p, p->cur.loc, "too many nested scopes"); - return 0; - } - scope = cfree_cg_scope_begin(p->cg, CFREE_CG_TYPE_NONE); - p->scopes[p->nscopes].cg_scope = scope; - p->nscopes++; - - cond_ty = toy_parse_expr(p); - if (cond_ty == CFREE_CG_TYPE_NONE) { - p->nscopes--; - return 0; - } - if (!toy_emit_truthy(p, cond_ty)) { - p->nscopes--; - return 0; - } - cfree_cg_break_false(p->cg, scope); - - if (!toy_parse_block(p)) { - p->nscopes--; - return 0; - } - - cfree_cg_continue(p->cg, scope); - cfree_cg_scope_end(p->cg, scope); - p->nscopes--; - return 1; -} - -static int toy_parse_break_stmt(ToyParser* p) { - toy_parser_advance(p); /* break */ - if (p->nscopes == 0) { - toy_error(p, p->cur.loc, "break outside loop"); - return 0; - } - cfree_cg_break(p->cg, p->scopes[p->nscopes - 1].cg_scope); - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after break"); - return 0; - } - return 1; -} - -static int toy_parse_continue_stmt(ToyParser* p) { - toy_parser_advance(p); /* continue */ - if (p->nscopes == 0) { - toy_error(p, p->cur.loc, "continue outside loop"); - return 0; - } - cfree_cg_continue(p->cg, p->scopes[p->nscopes - 1].cg_scope); - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after continue"); - return 0; - } - return 1; -} - -static int toy_parse_return_stmt(ToyParser* p) { - CfreeCgTypeId ty; - toy_parser_advance(p); /* return */ - if (toy_parser_match(p, TOK_TAIL)) { - CfreeSym name; - ToyFn* fn; - ToyToken call_tok; - CfreeCgTypeId fn_ty; - size_t nargs = 0; - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected function name after tail"); - return 0; - } - call_tok = p->cur; - name = toy_tok_sym(p, p->cur); - fn = toy_find_fn(p, name); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_LPAREN)) return 0; - if (fn) { - fn_ty = fn->type; - } else { - CfreeCgTypeId callee_ty = toy_push_named_rvalue(p, name); - if (callee_ty == CFREE_CG_TYPE_NONE) { - toy_error(p, call_tok.loc, "undefined function in tail call"); - return 0; - } - fn_ty = toy_ptr_pointee_func_type(p, callee_ty); - if (fn_ty == CFREE_CG_TYPE_NONE) { - toy_error(p, call_tok.loc, "tail callee is not a function pointer"); - return 0; - } - } - if (cfree_cg_type_func_is_variadic(p->c, fn_ty)) { - toy_error(p, call_tok.loc, "tail call to variadic function unsupported"); - return 0; - } - if (!toy_parse_call_args(p, call_tok, fn_ty, &nargs)) return 0; - if (cfree_cg_type_func_ret(p->c, fn_ty) != p->cur_fn_ret) { - toy_error(p, p->cur.loc, "tail call signature mismatch"); - return 0; - } - if (fn) - cfree_cg_tail_call_symbol(p->cg, fn->sym, (uint32_t)nargs); - else - cfree_cg_tail_call(p->cg, (uint32_t)nargs, fn_ty); - if (!toy_parser_expect(p, TOK_SEMI)) return 0; - return 1; - } - if (p->cur.kind == TOK_SEMI) { - toy_parser_advance(p); - if (p->cur_fn_ret != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { - toy_error(p, p->cur.loc, "return without value in non-void function"); - return 0; - } - cfree_cg_ret_void(p->cg); - return 1; - } - ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE) return 0; - if (ty != p->cur_fn_ret) { - toy_error(p, p->cur.loc, "return type mismatch"); - return 0; - } - cfree_cg_ret(p->cg); - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after return"); - return 0; - } - return 1; -} - -static int toy_parse_expr_stmt(ToyParser* p) { - CfreeCgTypeId ty = toy_parse_expr(p); - if (ty == CFREE_CG_TYPE_NONE) return 0; - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after expression"); - return 0; - } - if (ty != toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) cfree_cg_drop(p->cg); - return 1; -} - -static int toy_parse_stmt(ToyParser* p) { - toy_set_loc(p); - if (p->cur.kind == TOK_LET) return toy_parse_let_stmt(p); - if (p->cur.kind == TOK_IF) return toy_parse_if_stmt(p); - if (p->cur.kind == TOK_WHILE) return toy_parse_while_stmt(p); - if (p->cur.kind == TOK_BREAK) return toy_parse_break_stmt(p); - if (p->cur.kind == TOK_CONTINUE) return toy_parse_continue_stmt(p); - if (p->cur.kind == TOK_RETURN) return toy_parse_return_stmt(p); - if (p->cur.kind == TOK_LBRACE) return toy_parse_block(p); - - /* Assignment or expression statement */ - if (p->cur.kind == TOK_IDENT && - toy_lexer_peek(&p->lex).kind == TOK_EQ) { - CfreeSym name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); /* ident */ - toy_parser_advance(p); /* = */ - CfreeCgTypeId expr_ty = toy_parse_expr(p); - if (expr_ty == CFREE_CG_TYPE_NONE) return 0; - { - ToyVar* v = toy_find_var(p, name); - if (v) { - if (expr_ty != v->type) { - toy_error(p, p->cur.loc, "type mismatch in assignment"); - return 0; - } - toy_push_var_lvalue(p, v); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, v->type)); - } else { - ToyGlobal* g = toy_find_global(p, name); - if (!g) { - toy_error(p, p->cur.loc, "undefined variable in assignment"); - return 0; - } - if (!g->mutable) { - toy_error(p, p->cur.loc, "cannot assign to immutable global"); - return 0; - } - if (expr_ty != g->type) { - toy_error(p, p->cur.loc, "type mismatch in assignment"); - return 0; - } - cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, g->type)); - } - } - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after assignment"); - return 0; - } - return 1; - } - - if (p->cur.kind == TOK_STAR) { - CfreeCgTypeId ptr_ty; - CfreeCgTypeId expr_ty; - toy_parser_advance(p); /* * */ - ptr_ty = toy_parse_expr_unary(p); - if (ptr_ty == CFREE_CG_TYPE_NONE) return 0; - if (cfree_cg_type_kind(p->c, ptr_ty) != CFREE_CG_TYPE_PTR) { - toy_error(p, p->cur.loc, "cannot assign through non-pointer"); - return 0; - } - cfree_cg_indirect(p->cg); - if (!toy_parser_expect(p, TOK_EQ)) { - toy_error(p, p->cur.loc, "expected '=' in pointer assignment"); - return 0; - } - expr_ty = toy_parse_expr(p); - if (expr_ty == CFREE_CG_TYPE_NONE) return 0; - if (expr_ty != cfree_cg_type_ptr_pointee(p->c, ptr_ty)) { - toy_error(p, p->cur.loc, "type mismatch in pointer assignment"); - return 0; - } - cfree_cg_store(p->cg, toy_mem_access(p, expr_ty)); - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';' after assignment"); - return 0; - } - return 1; - } - - return toy_parse_expr_stmt(p); -} - -/* ============================================================ - * Function parsing - * ============================================================ */ - -static int toy_parse_fn(ToyParser* p) { - CfreeSym name; - CfreeCgTypeId ret_type; - CfreeCgTypeId param_types[TOY_MAX_PARAMS]; - CfreeCgFuncParam sig_params[TOY_MAX_PARAMS]; - CfreeSym param_names[TOY_MAX_PARAMS]; - size_t nparams = 0; - int variadic = 0; - CfreeCgDecl decl; - CfreeCgFuncSig sig; - CfreeCgTypeId fn_ty; - ToyFn* fn_entry; - size_t i; - - if (!toy_parser_match(p, TOK_FN)) return 0; - - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected function name"); - return -1; - } - name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - - if (!toy_parser_expect(p, TOK_LPAREN)) { - toy_error(p, p->cur.loc, "expected '(' after function name"); - return -1; - } - - if (p->cur.kind != TOK_RPAREN) { - for (;;) { - if (p->cur.kind == TOK_DOTDOTDOT) { - variadic = 1; - toy_parser_advance(p); - break; - } - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected parameter name"); - return -1; - } - if (nparams >= TOY_MAX_PARAMS) { - toy_error(p, p->cur.loc, "too many parameters"); - return -1; - } - param_names[nparams] = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_COLON)) { - toy_error(p, p->cur.loc, "expected ':' after parameter name"); - return -1; - } - param_types[nparams] = toy_parse_type(p); - if (param_types[nparams] == CFREE_CG_TYPE_NONE) return -1; - nparams++; - if (p->cur.kind == TOK_COMMA) { - toy_parser_advance(p); - if (p->cur.kind == TOK_DOTDOTDOT) { - variadic = 1; - toy_parser_advance(p); - break; - } - } else { - break; - } - } - } - if (!toy_parser_expect(p, TOK_RPAREN)) { - toy_error(p, p->cur.loc, "expected ')' after parameters"); - return -1; - } - - if (toy_parser_match(p, TOK_COLON)) { - ret_type = toy_parse_type(p); - if (ret_type == CFREE_CG_TYPE_NONE) return -1; - } else { - ret_type = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - } - - memset(sig_params, 0, sizeof sig_params); - for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i]; - memset(&sig, 0, sizeof sig); - sig.ret = ret_type; - sig.params = sig_params; - sig.nparams = (uint32_t)nparams; - sig.call_conv = CFREE_CG_CC_TARGET_C; - sig.abi_variadic = variadic; - - fn_ty = cfree_cg_type_func(p->c, sig); - if (fn_ty == CFREE_CG_TYPE_NONE) { - toy_error(p, p->cur.loc, "failed to create function type"); - return -1; - } - - memset(&decl, 0, sizeof(decl)); - decl.kind = CFREE_CG_DECL_FUNC; - decl.linkage_name = toy_c_linkage_name(p, name); - if (!decl.linkage_name) return -1; - decl.display_name = name; - decl.type = fn_ty; - decl.sym.bind = CFREE_SB_GLOBAL; - decl.sym.visibility = CFREE_CG_VIS_DEFAULT; - - /* Register function for recursion and later calls. */ - if (p->nfns >= TOY_MAX_FNS) { - toy_error(p, p->cur.loc, "too many functions"); - return -1; - } - fn_entry = &p->fns[p->nfns]; - fn_entry->name = name; - fn_entry->sym = cfree_cg_decl(p->cg, decl); - if (fn_entry->sym == CFREE_CG_SYM_NONE) { - toy_error(p, p->cur.loc, "failed to declare function"); - return -1; - } - fn_entry->type = fn_ty; - fn_entry->ret = ret_type; - fn_entry->nparams = nparams; - fn_entry->variadic = variadic; - for (i = 0; i < nparams; i++) fn_entry->params[i] = param_types[i]; - p->nfns++; - - cfree_cg_func_begin(p->cg, fn_entry->sym); - - /* Setup parameter slots */ - p->nvars = 0; - p->cur_fn_ret = ret_type; - for (i = 0; i < nparams; i++) { - CfreeCgLocal param = cfree_cg_param( - p->cg, (uint32_t)i, param_types[i], toy_slot_attrs(param_names[i])); - if (p->nvars >= TOY_MAX_VARS) { - toy_error(p, p->cur.loc, "too many vars"); - return -1; - } - p->vars[p->nvars].name = param_names[i]; - p->vars[p->nvars].type = param_types[i]; - p->vars[p->nvars].local = param; - p->nvars++; - } - - if (!toy_parse_block(p)) return -1; - - /* Implicit return for void functions */ - if (ret_type == toy_builtin_type(p, CFREE_CG_BUILTIN_VOID)) { - cfree_cg_ret_void(p->cg); - } - - cfree_cg_func_end(p->cg); - p->nvars = 0; - p->cur_fn_ret = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); - return 1; -} - -/* ============================================================ - * Global variable declarations - * ============================================================ */ - -static int toy_parse_global_var(ToyParser* p) { - CfreeSym name; - CfreeCgTypeId ty; - CfreeCgDecl decl; - CfreeCgDataDefAttrs data_attrs; - CfreeCgSym sym; - int mutable; - - if (p->cur.kind == TOK_VAR) { - mutable = 1; - toy_parser_advance(p); - } else { - mutable = 0; - toy_parser_advance(p); /* let */ - } - - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected identifier"); - return 0; - } - name = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - if (!toy_parser_expect(p, TOK_COLON)) { - toy_error(p, p->cur.loc, "expected ':' after identifier"); - return 0; - } - ty = toy_parse_type(p); - if (ty == CFREE_CG_TYPE_NONE) return 0; - - memset(&decl, 0, sizeof(decl)); - decl.kind = CFREE_CG_DECL_OBJECT; - decl.linkage_name = toy_c_linkage_name(p, name); - if (!decl.linkage_name) return 0; - decl.display_name = name; - decl.type = ty; - decl.sym.bind = CFREE_SB_GLOBAL; - decl.sym.visibility = CFREE_CG_VIS_DEFAULT; - decl.as.object.tls_model = CFREE_CG_TLS_AUTO; - if (!mutable) decl.as.object.flags |= CFREE_CG_OBJ_READONLY; - - sym = cfree_cg_decl(p->cg, decl); - if (sym == CFREE_CG_SYM_NONE) { - toy_error(p, p->cur.loc, "failed to declare global"); - return 0; - } - - if (p->nglobals >= TOY_MAX_GLOBALS) { - toy_error(p, p->cur.loc, "too many globals"); - return 0; - } - p->globals[p->nglobals].name = name; - p->globals[p->nglobals].sym = sym; - p->globals[p->nglobals].type = ty; - p->globals[p->nglobals].mutable = mutable; - p->nglobals++; - - memset(&data_attrs, 0, sizeof(data_attrs)); - - if (toy_parser_match(p, TOK_EQ)) { - if (p->cur.kind == TOK_NUMBER) { - uint8_t buf[16]; - size_t n = (size_t)cfree_cg_type_size(p->c, ty); - if (n > sizeof buf) { - toy_error(p, p->cur.loc, "initializer too large"); - return 0; - } - toy_emit_int_bytes(p, (uint64_t)p->cur.int_value, buf, n); - toy_parser_advance(p); - cfree_cg_data_begin(p->cg, sym, data_attrs); - cfree_cg_data_bytes(p->cg, buf, n); - cfree_cg_data_end(p->cg); - } else if (p->cur.kind == TOK_AMPERSAND) { - CfreeSym target; - CfreeCgSym target_sym; - uint32_t nbytes = (uint32_t)cfree_cg_type_size(p->c, ty); - toy_parser_advance(p); - if (p->cur.kind != TOK_IDENT) { - toy_error(p, p->cur.loc, "expected identifier after '&'"); - return 0; - } - target = toy_tok_sym(p, p->cur); - toy_parser_advance(p); - target_sym = toy_find_decl_sym(p, target); - if (target_sym == CFREE_CG_SYM_NONE) { - toy_error(p, p->cur.loc, "undefined symbol in initializer"); - return 0; - } - cfree_cg_data_begin(p->cg, sym, data_attrs); - cfree_cg_data_addr(p->cg, target_sym, 0, nbytes, 0); - cfree_cg_data_end(p->cg); - } else { - toy_error(p, p->cur.loc, "expected constant global initializer"); - return 0; - } - } else { - /* Zero-initialized */ - cfree_cg_data_begin(p->cg, sym, data_attrs); - cfree_cg_data_zero(p->cg, cfree_cg_type_size(p->c, ty)); - cfree_cg_data_end(p->cg); - } - - if (!toy_parser_expect(p, TOK_SEMI)) { - toy_error(p, p->cur.loc, "expected ';'"); - return 0; - } - return 1; -} - -/* ============================================================ - * Program parsing - * ============================================================ */ - -static int toy_parse_program(ToyParser* p) { - while (p->cur.kind != TOK_EOF) { - if (p->cur.kind == TOK_FN) { - int r = toy_parse_fn(p); - if (r < 0) return 0; - if (r == 0) { - toy_error(p, p->cur.loc, "expected function declaration"); - return 0; - } - } else if (p->cur.kind == TOK_LET || p->cur.kind == TOK_VAR) { - if (!toy_parse_global_var(p)) return 0; - } else { - toy_error(p, p->cur.loc, "expected function or global declaration"); - return 0; - } - } - return 1; -} - -/* ============================================================ - * Public entry point - * ============================================================ */ - -int cfree_toy_compile(CfreeCompiler* c, const CfreeCompileOptions* opts, - const CfreeBytesInput* input, CfreeObjBuilder* out) { - ToyParser p; - CfreeCg* cg; - const uint8_t* source; - - if (!c || !input || !out) return 1; - - source = input->data ? input->data : (const uint8_t*)""; - cg = cfree_cg_new(c, out, opts); - if (!cg) return 1; - - toy_parser_init(&p, c, cg, source, input->len); - if (!toy_parse_program(&p)) { - cfree_cg_free(cg); - return 1; - } - if (p.cur.kind != TOK_EOF) { - toy_error(&p, p.cur.loc, "unexpected token after program end"); - cfree_cg_free(cg); - return 1; - } - - cfree_cg_free(cg); - return 0; -} diff --git a/lang/toy/types.c b/lang/toy/types.c @@ -0,0 +1,776 @@ +#include "internal.h" + +#include <stdlib.h> +#include <string.h> + +static ToyType* toy_type_slot(ToyParser* p, ToyTypeId id) { + if (id == TOY_TYPE_NONE || id > p->type_table.ntypes) return NULL; + return &p->type_table.types[id - 1u]; +} + +static ToyTypeId toy_type_find(ToyParser* p, const ToyType* key) { + size_t i; + for (i = 0; i < p->type_table.ntypes; ++i) { + ToyType* ty = &p->type_table.types[i]; + if (ty->kind == key->kind && ty->cg == key->cg && + ty->name == key->name && ty->base == key->base && + ty->elem == key->elem && ty->pointee == key->pointee && + ty->ret == key->ret && ty->count == key->count && + ty->address_space == key->address_space && ty->quals == key->quals && + ty->nparams == key->nparams && ty->variadic == key->variadic) { + size_t j; + for (j = 0; j < key->nparams; ++j) { + if (ty->params[j] != key->params[j]) break; + } + if (j == key->nparams) return (ToyTypeId)(i + 1u); + } + } + return TOY_TYPE_NONE; +} + +static ToyTypeId toy_type_add(ToyParser* p, const ToyType* type) { + ToyTypeId existing = toy_type_find(p, type); + if (existing != TOY_TYPE_NONE) return existing; + if (!toy_parser_reserve(p, (void**)&p->type_table.types, + &p->type_table.cap_types, + p->type_table.ntypes + 1u, + sizeof *p->type_table.types, "toy types")) { + return TOY_TYPE_NONE; + } + p->type_table.types[p->type_table.ntypes] = *type; + p->type_table.ntypes++; + return (ToyTypeId)p->type_table.ntypes; +} + +static ToyTypeId toy_type_register(ToyParser* p, ToyTypeKind kind, + CfreeCgTypeId cg, CfreeSym name, + ToyTypeId base) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = kind; + type.cg = cg; + type.name = name; + type.base = base; + return toy_type_add(p, &type); +} + +static ToyTypeId toy_type_register_array(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId elem, uint64_t count) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_ARRAY; + type.cg = cg; + type.elem = elem; + type.count = count; + return toy_type_add(p, &type); +} + +static CfreeCgTypeId toy_type_finish(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId toy_type) { + p->last_type = toy_type; + return cg; +} + +CfreeCgTypeId toy_parse_type(ToyParser* p) { + if (p->cur.kind == TOK_IDENT && + ((p->cur.text_len == 5 && memcmp(p->cur.text, "const", 5) == 0) || + (p->cur.text_len == 8 && memcmp(p->cur.text, "volatile", 8) == 0) || + (p->cur.text_len == 8 && memcmp(p->cur.text, "restrict", 8) == 0))) { + int is_restrict = + p->cur.text_len == 8 && memcmp(p->cur.text, "restrict", 8) == 0; + uint32_t qual = is_restrict ? TOY_TYPE_QUAL_RESTRICT + : (p->cur.text_len == 5) ? TOY_TYPE_QUAL_CONST + : TOY_TYPE_QUAL_VOLATILE; + CfreeCgTypeId qualified_ty; + toy_parser_advance(p); + qualified_ty = toy_parse_type(p); + if (is_restrict && + cfree_cg_type_kind(p->c, qualified_ty) != CFREE_CG_TYPE_PTR) { + toy_error(p, p->cur.loc, "restrict requires pointer type"); + return CFREE_CG_TYPE_NONE; + } + return toy_type_finish( + p, qualified_ty, + toy_type_register_qualified(p, qualified_ty, qualified_ty, qual)); + } + if (toy_parser_match(p, TOK_FN)) { + CfreeCgTypeId* param_types = NULL; + CfreeCgFuncParam* sig_params = NULL; + CfreeCgFuncSig sig; + CfreeCgTypeId ret_type; + ToyTypeId ret_toy_type; + CfreeCgTypeId result = CFREE_CG_TYPE_NONE; + size_t nparams = 0; + size_t cap_param_types = 0; + ToyTypeId* param_toy_types = NULL; + size_t cap_param_toy_types = 0; + size_t cap_sig_params = 0; + int variadic = 0; + size_t i; + + if (!toy_parser_expect(p, TOK_LPAREN)) { + toy_error(p, p->cur.loc, "expected '(' after function type"); + goto fn_done; + } + if (p->cur.kind != TOK_RPAREN) { + for (;;) { + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } + if (!toy_parser_reserve(p, (void**)&param_types, &cap_param_types, + nparams + 1u, sizeof *param_types, + "function type parameters") || + !toy_parser_reserve(p, (void**)&param_toy_types, + &cap_param_toy_types, nparams + 1u, + sizeof *param_toy_types, + "function type source parameters") || + !toy_parser_reserve(p, (void**)&sig_params, &cap_sig_params, + nparams + 1u, sizeof *sig_params, + "function type signature parameters")) { + goto fn_done; + } + param_types[nparams] = toy_parse_type(p); + if (param_types[nparams] == CFREE_CG_TYPE_NONE) + goto fn_done; + param_toy_types[nparams] = p->last_type; + nparams++; + if (p->cur.kind == TOK_COMMA) { + toy_parser_advance(p); + if (p->cur.kind == TOK_DOTDOTDOT) { + variadic = 1; + toy_parser_advance(p); + break; + } + } else { + break; + } + } + } + if (!toy_parser_expect(p, TOK_RPAREN)) { + toy_error(p, p->cur.loc, "expected ')' after function type parameters"); + goto fn_done; + } + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' before function return type"); + goto fn_done; + } + ret_type = toy_parse_type(p); + if (ret_type == CFREE_CG_TYPE_NONE) goto fn_done; + ret_toy_type = p->last_type; + + for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i]; + memset(&sig, 0, sizeof sig); + sig.ret = ret_type; + sig.params = sig_params; + sig.nparams = (uint32_t)nparams; + sig.call_conv = CFREE_CG_CC_TARGET_C; + sig.abi_variadic = variadic; + { + CfreeCgTypeId fn_ty = cfree_cg_type_func(p->c, sig); + result = toy_type_finish( + p, fn_ty, + toy_type_register_func(p, fn_ty, ret_toy_type, param_toy_types, + nparams, variadic)); + } +fn_done: + free(param_types); + free(param_toy_types); + free(sig_params); + return result; + } + if (toy_parser_match(p, TOK_LBRACKET)) { + uint64_t count; + CfreeCgTypeId elem; + ToyTypeId elem_toy_type; + if (p->cur.kind != TOK_NUMBER || p->cur.is_float || + p->cur.int_value < 0) { + toy_error(p, p->cur.loc, "expected array count"); + return CFREE_CG_TYPE_NONE; + } + count = (uint64_t)p->cur.int_value; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after array count"); + return CFREE_CG_TYPE_NONE; + } + elem = toy_parse_type(p); + if (elem == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + elem_toy_type = p->last_type; + { + CfreeCgTypeId array_ty = cfree_cg_type_array(p->c, elem, count); + return toy_type_finish(p, array_ty, + toy_type_register_array(p, array_ty, + elem_toy_type, count)); + } + } + if (toy_parser_match(p, TOK_RECORD)) { + CfreeCgField* fields = NULL; + size_t nfields = 0; + size_t cap_fields = 0; + int packed = 0; + uint32_t record_align = 0; + CfreeCgTypeId result = CFREE_CG_TYPE_NONE; + if (!toy_parse_record_attr_list(p, &packed, &record_align)) + return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_LBRACE)) { + toy_error(p, p->cur.loc, "expected '{' after record type"); + goto record_done; + } + while (p->cur.kind != TOK_RBRACE && p->cur.kind != TOK_EOF) { + if (!toy_parser_reserve(p, (void**)&fields, &cap_fields, nfields + 1u, + sizeof *fields, "record type fields")) { + goto record_done; + } + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "expected record field name"); + goto record_done; + } + fields[nfields].name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parse_field_attr_list(p, &fields[nfields])) + goto record_done; + if (!toy_parser_expect(p, TOK_COLON)) { + toy_error(p, p->cur.loc, "expected ':' after record field name"); + goto record_done; + } + fields[nfields].type = toy_parse_type(p); + if (fields[nfields].type == CFREE_CG_TYPE_NONE) + goto record_done; + nfields++; + if (!toy_parser_match(p, TOK_COMMA)) break; + } + if (!toy_parser_expect(p, TOK_RBRACE)) { + toy_error(p, p->cur.loc, "expected '}' after record type"); + goto record_done; + } + if (packed) { + size_t i; + for (i = 0; i < nfields; ++i) { + if (!fields[i].align_override) fields[i].align_override = 1; + } + } + if (record_align) { + CfreeCgRecordDesc desc; + CfreeCgTypeId record_ty; + memset(&desc, 0, sizeof desc); + desc.fields = fields; + desc.nfields = (uint32_t)nfields; + desc.align_override = record_align; + record_ty = cfree_cg_type_record_ex(p->c, &desc); + result = toy_type_finish(p, record_ty, toy_type_from_cg(p, record_ty)); + goto record_done; + } + { + CfreeCgTypeId record_ty = + cfree_cg_type_record(p->c, 0, fields, (uint32_t)nfields); + result = toy_type_finish(p, record_ty, toy_type_from_cg(p, record_ty)); + } +record_done: + free(fields); + return result; + } + if (p->cur.kind == TOK_INT) { + toy_error(p, p->cur.loc, + "legacy int type is unsupported; use an explicit scalar type"); + return CFREE_CG_TYPE_NONE; + } + if (p->cur.kind == TOK_IDENT) { + CfreeCgTypeId ty = CFREE_CG_TYPE_NONE; + if (p->cur.text_len == 4 && memcmp(p->cur.text, "void", 4) == 0) + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_VOID); + else if (p->cur.text_len == 4 && memcmp(p->cur.text, "bool", 4) == 0) + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_BOOL); + else if (p->cur.text_len == 2 && p->cur.text[0] == 'i' && + p->cur.text[1] == '8') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + else if (p->cur.text_len == 2 && p->cur.text[0] == 'u' && + p->cur.text[1] == '8') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I8); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && + p->cur.text[1] == '1' && p->cur.text[2] == '6') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I16); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && + p->cur.text[1] == '1' && p->cur.text[2] == '6') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I16); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && + p->cur.text[1] == '3' && p->cur.text[2] == '2') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I32); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && + p->cur.text[1] == '3' && p->cur.text[2] == '2') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I32); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'i' && + p->cur.text[1] == '6' && p->cur.text[2] == '4') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); + else if (p->cur.text_len == 3 && p->cur.text[0] == 'u' && + p->cur.text[1] == '6' && p->cur.text[2] == '4') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I64); + else if (p->cur.text_len == 4 && p->cur.text[0] == 'i' && + p->cur.text[1] == '1' && p->cur.text[2] == '2' && + p->cur.text[3] == '8') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I128); + else if (p->cur.text_len == 4 && p->cur.text[0] == 'u' && + p->cur.text[1] == '1' && p->cur.text[2] == '2' && + p->cur.text[3] == '8') + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_I128); + else if (p->cur.text_len == 5 && memcmp(p->cur.text, "isize", 5) == 0) + ty = p->int_type; + else if (p->cur.text_len == 5 && memcmp(p->cur.text, "usize", 5) == 0) + ty = p->int_type; + else if (p->cur.text_len == 3 && memcmp(p->cur.text, "f32", 3) == 0) + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_F32); + else if (p->cur.text_len == 3 && memcmp(p->cur.text, "f64", 3) == 0) + ty = toy_builtin_type(p, CFREE_CG_BUILTIN_F64); + else if (p->cur.text_len == 7 && memcmp(p->cur.text, "va_list", 7) == 0) + ty = p->va_list_type; + else { + ToyNamedType* named = toy_find_named_type(p, toy_tok_sym(p, p->cur)); + if (named) { + if (named->type == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "incomplete record type requires pointer"); + return CFREE_CG_TYPE_NONE; + } + ty = named->type; + } + } + if (ty != CFREE_CG_TYPE_NONE) { + ToyTypeId toy_type = toy_type_from_cg(p, ty); + ToyNamedType* named = + (p->cur.kind == TOK_IDENT) ? toy_find_named_type(p, toy_tok_sym(p, p->cur)) : NULL; + if (named && named->type == ty && named->toy_type != TOY_TYPE_NONE) + toy_type = named->toy_type; + toy_parser_advance(p); + return toy_type_finish(p, ty, toy_type); + } + } + if (toy_parser_match(p, TOK_STAR)) { + uint32_t address_space = 0; + ToyNamedType* incomplete_pointee = NULL; + if (p->cur.kind == TOK_IDENT && p->cur.text_len == 9 && + memcmp(p->cur.text, "addrspace", 9) == 0) { + int64_t as_value; + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_number_arg(p, &as_value) || + !toy_parser_expect(p, TOK_RPAREN) || as_value < 0) { + toy_error(p, p->cur.loc, "invalid address space"); + return CFREE_CG_TYPE_NONE; + } + address_space = (uint32_t)as_value; + } + if (p->cur.kind == TOK_IDENT) { + incomplete_pointee = toy_find_named_type(p, toy_tok_sym(p, p->cur)); + if (incomplete_pointee && + incomplete_pointee->type == CFREE_CG_TYPE_NONE) { + CfreeCgTypeId ptr_ty; + toy_parser_advance(p); + ptr_ty = cfree_cg_type_ptr( + p->c, toy_builtin_type(p, CFREE_CG_BUILTIN_VOID), address_space); + return toy_type_finish( + p, ptr_ty, + toy_type_register_ptr(p, ptr_ty, incomplete_pointee->toy_type, + address_space)); + } + } + { + CfreeCgTypeId pointee = toy_parse_type(p); + ToyTypeId pointee_toy_type; + if (pointee == CFREE_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "expected type after '*'"); + return CFREE_CG_TYPE_NONE; + } + pointee_toy_type = p->last_type; + { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, pointee, address_space); + return toy_type_finish( + p, ptr_ty, + toy_type_register_ptr(p, ptr_ty, pointee_toy_type, address_space)); + } + } + } + toy_error(p, p->cur.loc, "expected type"); + return CFREE_CG_TYPE_NONE; +} + +int toy_type_is_intlike(ToyParser* p, CfreeCgTypeId ty) { + CfreeCgTypeKind k = cfree_cg_type_kind(p->c, ty); + return k == CFREE_CG_TYPE_INT || k == CFREE_CG_TYPE_BOOL || + k == CFREE_CG_TYPE_ENUM; +} + +int toy_type_is_float(ToyParser* p, CfreeCgTypeId ty) { + return cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_FLOAT; +} + +int toy_type_is_ptr(ToyParser* p, CfreeCgTypeId ty) { + return cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR; +} + +uint32_t toy_type_int_width(ToyParser* p, CfreeCgTypeId ty) { + if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_BOOL) return 1; + return cfree_cg_type_int_width(p->c, ty); +} + +CfreeCgTypeId toy_ptr_pointee_func_type(ToyParser* p, CfreeCgTypeId ptr_ty) { + CfreeCgTypeId fn_ty; + if (cfree_cg_type_kind(p->c, ptr_ty) != CFREE_CG_TYPE_PTR) + return CFREE_CG_TYPE_NONE; + fn_ty = cfree_cg_type_ptr_pointee(p->c, ptr_ty); + if (cfree_cg_type_kind(p->c, fn_ty) != CFREE_CG_TYPE_FUNC) + return CFREE_CG_TYPE_NONE; + return fn_ty; +} + +void toy_type_register_builtins(ToyParser* p) { + size_t i; + for (i = 0; i < CFREE_CG_BUILTIN_COUNT; ++i) + (void)toy_type_from_cg(p, p->types.id[i]); + (void)toy_type_from_cg(p, p->int_ptr_type); +} + +const ToyType* toy_type_get(ToyParser* p, ToyTypeId id) { + return toy_type_slot(p, id); +} + +CfreeCgTypeId toy_type_cg(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + return type ? type->cg : CFREE_CG_TYPE_NONE; +} + +ToyTypeId toy_type_from_cg(ToyParser* p, CfreeCgTypeId cg) { + ToyType type; + CfreeCgTypeKind kind; + size_t i; + if (cg == CFREE_CG_TYPE_NONE) return TOY_TYPE_NONE; + for (i = 0; i < p->type_table.ntypes; ++i) { + ToyType* existing = &p->type_table.types[i]; + if (existing->cg == cg && + (existing->kind == TOY_TYPE_NOMINAL_RECORD || + existing->kind == TOY_TYPE_TUPLE_RECORD || + existing->kind == TOY_TYPE_ENUM)) { + return (ToyTypeId)(i + 1u); + } + if (existing->cg == cg && existing->name == 0 && + (existing->kind == TOY_TYPE_BUILTIN || + existing->kind == TOY_TYPE_ARRAY || existing->kind == TOY_TYPE_PTR || + existing->kind == TOY_TYPE_FUNC || + existing->kind == TOY_TYPE_ANON_RECORD)) { + return (ToyTypeId)(i + 1u); + } + } + + memset(&type, 0, sizeof type); + type.cg = cg; + kind = cfree_cg_type_kind(p->c, cg); + switch (kind) { + case CFREE_CG_TYPE_VOID: + case CFREE_CG_TYPE_BOOL: + case CFREE_CG_TYPE_INT: + case CFREE_CG_TYPE_FLOAT: + case CFREE_CG_TYPE_VARARG_STATE: + type.kind = TOY_TYPE_BUILTIN; + break; + case CFREE_CG_TYPE_PTR: + type.kind = TOY_TYPE_PTR; + type.pointee = toy_type_from_cg(p, cfree_cg_type_ptr_pointee(p->c, cg)); + type.address_space = cfree_cg_type_ptr_address_space(p->c, cg); + break; + case CFREE_CG_TYPE_ARRAY: + type.kind = TOY_TYPE_ARRAY; + type.elem = toy_type_from_cg(p, cfree_cg_type_array_elem(p->c, cg)); + type.count = cfree_cg_type_array_count(p->c, cg); + break; + case CFREE_CG_TYPE_FUNC: + type.kind = TOY_TYPE_FUNC; + type.ret = toy_type_from_cg(p, cfree_cg_type_func_ret(p->c, cg)); + type.count = cfree_cg_type_func_nparams(p->c, cg); + break; + case CFREE_CG_TYPE_RECORD: + type.kind = TOY_TYPE_ANON_RECORD; + type.count = cfree_cg_type_record_nfields(p->c, cg); + break; + case CFREE_CG_TYPE_ENUM: + type.kind = TOY_TYPE_ENUM; + break; + case CFREE_CG_TYPE_ALIAS: + type.kind = TOY_TYPE_ALIAS; + break; + } + return toy_type_add(p, &type); +} + +ToyTypeId toy_type_register_alias(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, CfreeCgTypeId base) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_ALIAS; + type.cg = cg; + type.name = name; + type.base = toy_type_from_cg(p, base); + return toy_type_add(p, &type); +} + +ToyTypeId toy_type_register_named_record(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, int is_tuple) { + ToyTypeKind kind = is_tuple ? TOY_TYPE_TUPLE_RECORD : TOY_TYPE_NOMINAL_RECORD; + size_t i; + for (i = 0; i < p->type_table.ntypes; ++i) { + ToyType* type = &p->type_table.types[i]; + if (type->kind == kind && type->name == name) { + if (type->cg == CFREE_CG_TYPE_NONE && cg != CFREE_CG_TYPE_NONE) + type->cg = cg; + return (ToyTypeId)(i + 1u); + } + } + return toy_type_register(p, kind, cg, name, TOY_TYPE_NONE); +} + +ToyTypeId toy_type_register_enum(ToyParser* p, CfreeSym name, + CfreeCgTypeId cg, CfreeCgTypeId base) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_ENUM; + type.cg = cg; + type.name = name; + type.base = toy_type_from_cg(p, base); + return toy_type_add(p, &type); +} + +ToyTypeId toy_type_register_qualified(ToyParser* p, CfreeCgTypeId cg, + CfreeCgTypeId base, uint32_t quals) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_QUALIFIED; + type.cg = cg; + type.base = toy_type_from_cg(p, base); + type.quals = quals; + return toy_type_add(p, &type); +} + +ToyTypeId toy_type_register_ptr(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId pointee, uint32_t address_space) { + ToyType type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_PTR; + type.cg = cg; + type.pointee = pointee; + type.address_space = address_space; + return toy_type_add(p, &type); +} + +ToyTypeId toy_type_register_func(ToyParser* p, CfreeCgTypeId cg, + ToyTypeId ret, const ToyTypeId* params, + size_t nparams, int variadic) { + ToyType type; + ToyTypeId id; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_FUNC; + type.cg = cg; + type.ret = ret; + type.count = nparams; + type.nparams = nparams; + type.variadic = variadic; + if (nparams) { + type.params = (ToyTypeId*)calloc(nparams, sizeof *type.params); + if (!type.params) return TOY_TYPE_NONE; + memcpy(type.params, params, nparams * sizeof *type.params); + } + id = toy_type_add(p, &type); + if (id == TOY_TYPE_NONE || p->type_table.types[id - 1u].params != type.params) + free(type.params); + return id; +} + +CfreeCgTypeId toy_type_resolved_cg(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + if (!type) return CFREE_CG_TYPE_NONE; + if (type->kind == TOY_TYPE_ALIAS || type->kind == TOY_TYPE_QUALIFIED) + return toy_type_resolved_cg(p, type->base); + if (type->kind == TOY_TYPE_PTR && type->pointee != TOY_TYPE_NONE) { + CfreeCgTypeId pointee = toy_type_resolved_cg(p, type->pointee); + if (pointee != CFREE_CG_TYPE_NONE) + return cfree_cg_type_ptr(p->c, pointee, type->address_space); + } + return type->cg; +} + +ToyTypeId toy_type_pointee(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + if (!type) return TOY_TYPE_NONE; + if (type->kind == TOY_TYPE_ALIAS || type->kind == TOY_TYPE_QUALIFIED) + return toy_type_pointee(p, type->base); + return type->kind == TOY_TYPE_PTR ? type->pointee : TOY_TYPE_NONE; +} + +ToyTypeId toy_type_array_elem(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + if (!type) return TOY_TYPE_NONE; + if (type->kind == TOY_TYPE_ALIAS || type->kind == TOY_TYPE_QUALIFIED) + return toy_type_array_elem(p, type->base); + return type->kind == TOY_TYPE_ARRAY ? type->elem : TOY_TYPE_NONE; +} + +static int toy_anon_record_types_match(ToyParser* p, CfreeCgTypeId expected, + CfreeCgTypeId actual) { + uint32_t i; + uint32_t nfields; + if (cfree_cg_type_kind(p->c, expected) != CFREE_CG_TYPE_RECORD || + cfree_cg_type_kind(p->c, actual) != CFREE_CG_TYPE_RECORD) { + return 0; + } + nfields = cfree_cg_type_record_nfields(p->c, expected); + if (nfields != cfree_cg_type_record_nfields(p->c, actual)) return 0; + for (i = 0; i < nfields; ++i) { + CfreeCgField exp_field; + CfreeCgField act_field; + if (cfree_cg_type_record_field(p->c, expected, i, &exp_field, NULL) != 0 || + cfree_cg_type_record_field(p->c, actual, i, &act_field, NULL) != 0) { + return 0; + } + if (exp_field.name != act_field.name) return 0; + if (!toy_type_accepts_type(p, toy_type_from_cg(p, exp_field.type), + toy_type_from_cg(p, act_field.type))) { + return 0; + } + } + return 1; +} + +int toy_type_accepts_type(ToyParser* p, ToyTypeId expected, ToyTypeId actual) { + const ToyType* exp = toy_type_get(p, expected); + const ToyType* act = toy_type_get(p, actual); + CfreeCgTypeId exp_cg, act_cg; + if (!exp || !act) return 0; + if (expected == actual) return 1; + if (exp->kind == TOY_TYPE_ALIAS || exp->kind == TOY_TYPE_QUALIFIED) + return toy_type_accepts_type(p, exp->base, actual); + if (act->kind == TOY_TYPE_ALIAS || act->kind == TOY_TYPE_QUALIFIED) + return toy_type_accepts_type(p, expected, act->base); + if (exp->kind == TOY_TYPE_PTR && act->kind == TOY_TYPE_PTR) { + if (exp->address_space != act->address_space) return 0; + if (exp->pointee == TOY_TYPE_NONE || act->pointee == TOY_TYPE_NONE) + return exp->cg == act->cg; + return toy_type_accepts_type(p, exp->pointee, act->pointee); + } + if (exp->kind == TOY_TYPE_ARRAY && act->kind == TOY_TYPE_ARRAY) { + return exp->count == act->count && + toy_type_accepts_type(p, exp->elem, act->elem); + } + if (exp->kind == TOY_TYPE_FUNC && act->kind == TOY_TYPE_FUNC) { + size_t i; + if (exp->nparams != act->nparams || exp->variadic != act->variadic) + return 0; + if (!toy_type_accepts_type(p, exp->ret, act->ret)) return 0; + if (!exp->params || !act->params) return exp->cg == act->cg; + for (i = 0; i < exp->nparams; ++i) { + if (!toy_type_accepts_type(p, exp->params[i], act->params[i])) return 0; + } + return 1; + } + if (exp->kind == TOY_TYPE_ANON_RECORD && act->kind == TOY_TYPE_ANON_RECORD) + return toy_anon_record_types_match(p, exp->cg, act->cg); + exp_cg = toy_type_resolved_cg(p, expected); + act_cg = toy_type_resolved_cg(p, actual); + return exp_cg != CFREE_CG_TYPE_NONE && exp_cg == act_cg && + exp->kind != TOY_TYPE_NOMINAL_RECORD && + exp->kind != TOY_TYPE_TUPLE_RECORD && exp->kind != TOY_TYPE_ENUM; +} + +int toy_record_field_index(ToyParser* p, CfreeCgTypeId record_ty, + CfreeSym field_name, uint32_t* index_out, + CfreeCgField* field_out) { + uint32_t i, nfields = cfree_cg_type_record_nfields(p->c, record_ty); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, record_ty, i, &field, NULL) == 0 && + field.name == field_name) { + if (index_out) *index_out = i; + if (field_out) *field_out = field; + return 1; + } + } + return 0; +} + +ToyNamedType* toy_find_named_type(ToyParser* p, CfreeSym name) { + size_t i; + for (i = p->type_table.count; i > 0; --i) { + if (p->type_table.named[i - 1].name == name) + return &p->type_table.named[i - 1]; + } + return NULL; +} + +ToyNamedType* toy_find_named_type_by_type(ToyParser* p, CfreeCgTypeId type) { + size_t i; + for (i = p->type_table.count; i > 0; --i) { + if (p->type_table.named[i - 1].type == type) + return &p->type_table.named[i - 1]; + } + return NULL; +} + +int toy_add_named_type(ToyParser* p, CfreeSym name, CfreeCgTypeId type, + ToyNamedTypeKind kind, CfreeCgTypeId base_type) { + ToyNamedType* existing = toy_find_named_type(p, name); + ToyTypeId toy_type = TOY_TYPE_NONE; + if (kind == TOY_NAMED_ALIAS) + toy_type = toy_type_register_alias(p, name, type, base_type); + else if (kind == TOY_NAMED_ENUM) + toy_type = toy_type_register_enum(p, name, type, base_type); + else + toy_type = toy_type_register_named_record( + p, name, type, kind == TOY_NAMED_TUPLE); + if (toy_type == TOY_TYPE_NONE && type != CFREE_CG_TYPE_NONE) return 0; + if (existing && existing->type == CFREE_CG_TYPE_NONE) { + existing->type = type; + existing->toy_type = toy_type; + existing->kind = kind; + existing->base_type = base_type; + return 1; + } + if (!toy_parser_reserve(p, (void**)&p->type_table.named, + &p->type_table.cap, p->type_table.count + 1u, + sizeof *p->type_table.named, + "named types")) { + return 0; + } + memset(&p->type_table.named[p->type_table.count], 0, + sizeof p->type_table.named[p->type_table.count]); + p->type_table.named[p->type_table.count].name = name; + p->type_table.named[p->type_table.count].type = type; + p->type_table.named[p->type_table.count].toy_type = toy_type; + p->type_table.named[p->type_table.count].kind = kind; + p->type_table.named[p->type_table.count].base_type = base_type; + p->type_table.count++; + return 1; +} + +int toy_set_named_type_fields(ToyParser* p, ToyNamedType* named, + const ToyRecordFieldInfo* fields, + size_t nfields) { + if (!toy_parser_reserve(p, (void**)&named->fields, &named->cap_fields, + nfields, sizeof *named->fields, "record fields")) { + return 0; + } + if (nfields) + memcpy(named->fields, fields, sizeof *named->fields * nfields); + named->nfields = nfields; + return 1; +} + +int toy_set_named_type_enum_values(ToyParser* p, ToyNamedType* named, + const ToyEnumConst* values, + size_t nvalues) { + if (!named) return 0; + if (!toy_parser_reserve(p, (void**)&named->enum_values, + &named->cap_enum_values, nvalues, + sizeof *named->enum_values, "enum values")) { + return 0; + } + if (nvalues) + memcpy(named->enum_values, values, sizeof values[0] * nvalues); + named->nenum_values = nvalues; + return 1; +} diff --git a/src/api/cg.c b/src/api/cg.c @@ -4615,26 +4615,33 @@ void cfree_cg_switch(CfreeCg *g, CfreeCgSwitch sw) { void cfree_cg_push_label_addr(CfreeCg *g, CfreeCgLabel label, CfreeCgTypeId ptr_type) { - (void)label; + CfreeCgTypeId ty; if (!g) return; - compiler_panic(g->c, g->cur_loc, - "CfreeCg: label addresses are unsupported by CGTarget"); - cfree_cg_push_null(g, ptr_type); + ty = resolve_type(g->c, ptr_type); + if (!ty) + return; + api_push(g, api_make_sv(api_op_imm((i64)label, ty), ty)); } void cfree_cg_computed_goto(CfreeCg *g, const CfreeCgLabel *valid_targets, uint32_t ntargets) { ApiSValue target; - (void)valid_targets; - (void)ntargets; + CfreeCgTypeId target_ty; + Operand target_op; if (!g) return; api_local_const_control_boundary(g); target = api_pop(g); + target_ty = api_sv_type(&target); + target_op = api_force_reg(g, &target, target_ty); + for (uint32_t i = 0; i < ntargets; ++i) { + Operand imm = api_op_imm((i64)valid_targets[i], target_ty); + g->target->cmp_branch(g->target, CMP_EQ, target_op, imm, + (Label)valid_targets[i]); + } api_release(g, &target); - compiler_panic(g->c, g->cur_loc, - "CfreeCg: computed goto is unsupported by CGTarget"); + g->target->intrinsic(g->target, INTRIN_UNREACHABLE, NULL, 0, NULL, 0); } void cfree_cg_unreachable(CfreeCg *g) { @@ -5612,6 +5619,9 @@ void cfree_cg_data_begin(CfreeCg *g, CfreeCgSym cg_sym, if (attrs.flags & CFREE_CG_DATADEF_ZERO_FILL) { sec = obj_section_ex(ob, sec_name_sym, sec_kind, SSEM_NOBITS, sec_flags, align, 0, OBJ_SEC_NONE, 0); + } else if (attrs.entsize) { + sec = obj_section_ex(ob, sec_name_sym, sec_kind, SSEM_PROGBITS, sec_flags, + align, attrs.entsize, OBJ_SEC_NONE, 0); } else { sec = obj_section(ob, sec_name_sym, sec_kind, sec_flags, align); } @@ -5782,14 +5792,14 @@ void cfree_cg_data_addr(CfreeCg *g, CfreeCgSym target, int64_t addend, void cfree_cg_data_label_addr(CfreeCg *g, CfreeCgLabel target, int64_t addend, uint32_t width, uint32_t address_space) { u8 pad[8]; - (void)target; - (void)addend; (void)address_space; if (!g || !width || width > sizeof(pad)) return; - compiler_panic(g->c, g->cur_loc, - "CfreeCg: data label addresses are unsupported by ObjBuilder"); memset(pad, 0, sizeof(pad)); + for (u32 i = 0; i < width; ++i) { + u32 shift = g->c->target.big_endian ? (width - 1u - i) * 8u : i * 8u; + pad[i] = (u8)(((uint64_t)target + (uint64_t)addend) >> shift); + } obj_write(g->obj, g->data_sec, pad, width); g->data_size += width; } diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -740,6 +740,7 @@ CfreeObjSecInfo cfree_obj_section(const CfreeObjFile* f, uint32_t idx) { out.flags = sec ? (uint32_t)sec->flags : 0u; out.size = sec ? (sec->bss_size ? sec->bss_size : sec->bytes.total) : 0u; out.align = (sec && sec->align > 1u) ? sec->align : 1u; + out.entsize = sec ? sec->entsize : 0u; return out; } diff --git a/src/arch/aa64/ops.c b/src/arch/aa64/ops.c @@ -1381,6 +1381,17 @@ static void aa_va_start_(CGTarget* t, Operand ap_op) { compiler_panic(t->c, a->loc, "aarch64 va_start: function not variadic"); } u32 ap = reg_num(ap_op); + if (t->c->target.os == CFREE_OS_MACOS) { + u32 ofs = 16u + a->next_param_stack; + if (ofs <= 0xfff) + aa64_emit32(mc, aa64_add_imm(1, AA_TMP0, 29, ofs, 0)); + else { + aa64_emit_load_imm(mc, 1, AA_TMP0, (i64)ofs); + aa64_emit32(mc, aa64_add(1, AA_TMP0, 29, AA_TMP0)); + } + aa64_emit32(mc, aa64_str_uimm(3, AA_TMP0, ap, 0)); + return; + } AASlot* gs = aa64_slot_get(a, a->gp_save_slot); AASlot* fs = aa64_slot_get(a, a->fp_save_slot); @@ -1417,6 +1428,17 @@ static void aa_va_arg_(CGTarget* t, Operand dst, Operand ap_op, u32 sz = type_byte_size(ty); u32 sidx = size_idx_for_bytes(sz); + if (t->c->target.os == CFREE_OS_MACOS) { + aa64_emit32(mc, aa64_ldur(3, AA_TMP1, ap, 0)); + if (is_fp) + aa64_emit32(mc, aa64_ldur_fp(sidx, reg_num(dst), AA_TMP1, 0)); + else + aa64_emit32(mc, aa64_ldur(sidx, reg_num(dst), AA_TMP1, 0)); + aa64_emit32(mc, aa64_add_imm(1, AA_TMP1, AA_TMP1, 8u, 0)); + aa64_emit32(mc, aa64_stur(3, AA_TMP1, ap, 0)); + return; + } + MCLabel L_stack = mc->label_new(mc); MCLabel L_done = mc->label_new(mc); @@ -1458,6 +1480,11 @@ static void aa_va_copy_(CGTarget* t, Operand d, Operand s) { MCEmitter* mc = t->mc; u32 dr = reg_num(d); u32 sr = reg_num(s); + if (t->c->target.os == CFREE_OS_MACOS) { + aa64_emit32(mc, aa64_ldur(3, AA_TMP0, sr, 0)); + aa64_emit32(mc, aa64_stur(3, AA_TMP0, dr, 0)); + return; + } for (u32 i = 0; i < 32u; i += 8u) { aa64_emit32(mc, aa64_ldur(3, AA_TMP0, sr, (i32)i)); aa64_emit32(mc, aa64_stur(3, AA_TMP0, dr, (i32)i)); diff --git a/src/obj/macho_emit.c b/src/obj/macho_emit.c @@ -146,6 +146,7 @@ typedef struct MSec { u32 reloff; /* 0 if no relocs */ u32 nreloc; u32 flags; /* S_TYPE | S_ATTR_* */ + u32 entsize; u32 obj_sec; /* originating ObjSecId */ int is_zerofill; const Buf* obj_bytes; /* NULL when zerofill */ @@ -179,6 +180,9 @@ static u32 section_flags_for(u16 sec_kind, u16 sec_flags, const char* sectname, if (sec_kind == SEC_BSS || (sect_len >= 5 && memcmp(sectname, "__bss", 5) == 0)) { f |= S_ZEROFILL; } + if (sec_flags & SF_STRINGS) { + f = (f & ~SECTION_TYPE) | S_CSTRING_LITERALS; + } /* Default S_REGULAR (0) for all others. */ return f; } @@ -261,6 +265,7 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { name_to_seg_sect(nm ? nm : "", (u32)nlen, s->kind, &m->ns); m->obj_sec = i; m->align = s->align ? s->align : 1; + m->entsize = s->entsize; /* Mach-O reader stashes the raw section.flags (S_TYPE | S_ATTR_*) * in Section.ext_type when reading a Mach-O input. Use it * verbatim so attribute bits like S_ATTR_NO_DEAD_STRIP / @@ -499,6 +504,60 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { for (u32 ri = 0; ri < total_relocs; ++ri) { const Reloc* r = obj_reloc_at(ob, ri); if (r->section_id != m->obj_sec) continue; + if ((r->kind == R_RV_ADD8 || r->kind == R_RV_ADD16 || + r->kind == R_RV_ADD32 || r->kind == R_RV_ADD64) && + ri + 1u < total_relocs) { + const Reloc* sub = obj_reloc_at(ob, ri + 1u); + int paired = + sub && sub->section_id == r->section_id && + sub->offset == r->offset && + ((r->kind == R_RV_ADD8 && sub->kind == R_RV_SUB8) || + (r->kind == R_RV_ADD16 && sub->kind == R_RV_SUB16) || + (r->kind == R_RV_ADD32 && sub->kind == R_RV_SUB32) || + (r->kind == R_RV_ADD64 && sub->kind == R_RV_SUB64)); + if (paired) { + u32 length = (r->kind == R_RV_ADD64) ? 3u + : (r->kind == R_RV_ADD32) ? 2u + : (r->kind == R_RV_ADD16) ? 1u + : 0u; + u32 add_idx; + u32 sub_idx; + u32 sub_type = c->target.arch == CFREE_ARCH_ARM_64 + ? ARM64_RELOC_SUBTRACTOR + : X86_64_RELOC_SUBTRACTOR; + u32 unsigned_type = c->target.arch == CFREE_ARCH_ARM_64 + ? ARM64_RELOC_UNSIGNED + : X86_64_RELOC_UNSIGNED; + if (r->sym == OBJ_SYM_NONE || sub->sym == OBJ_SYM_NONE) { + compiler_panic(c, no_loc(), + "emit_macho: symdiff reloc without symbol"); + } + add_idx = sym_obj_to_macho[r->sym]; + sub_idx = sym_obj_to_macho[sub->sym]; + if (add_idx == 0 || sub_idx == 0) { + compiler_panic(c, no_loc(), + "emit_macho: symdiff reloc target not in symtab"); + } + { + u8* slot = buf + (size_t)j * MACHO_RELOC_SIZE; + wr_u32_le(slot + 0, (u32)r->offset); + wr_u32_le(slot + 4, ((sub_idx - 1u) & 0x00ffffffu) | + (length << 25) | (1u << 27) | + ((sub_type & 0xfu) << 28)); + ++j; + } + { + u8* slot = buf + (size_t)j * MACHO_RELOC_SIZE; + wr_u32_le(slot + 0, (u32)r->offset); + wr_u32_le(slot + 4, ((add_idx - 1u) & 0x00ffffffu) | + (length << 25) | (1u << 27) | + ((unsigned_type & 0xfu) << 28)); + ++j; + } + ++ri; + continue; + } + } u32 mtype = reloc_to(r->kind); if (mtype == (u32)-1) { compiler_panic(c, no_loc(), @@ -647,7 +706,7 @@ void emit_macho(Compiler* c, ObjBuilder* ob, Writer* w) { wr_u32(w, m->nreloc); wr_u32(w, m->flags); wr_u32(w, 0); /* reserved1 */ - wr_u32(w, 0); /* reserved2 */ + wr_u32(w, m->entsize); /* reserved2 */ wr_u32(w, 0); /* reserved3 */ } diff --git a/src/obj/macho_read.c b/src/obj/macho_read.c @@ -38,6 +38,7 @@ typedef struct MSecRec { u32 reloff; u32 nreloc; u32 flags; + u32 reserved2; ObjSecId obj_sec; /* assigned in pass 1 */ } MSecRec; @@ -80,6 +81,9 @@ static u16 sec_flags_from(u32 mflags, u16 sec_kind) { stype == S_THREAD_LOCAL_VARIABLES) { f |= SF_TLS; } + if (stype == S_CSTRING_LITERALS) { + f |= SF_MERGE | SF_STRINGS; + } return f; } @@ -158,6 +162,7 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, m->reloff = rd_u32_le(sp + 56); m->nreloc = rd_u32_le(sp + 60); m->flags = rd_u32_le(sp + 64); + m->reserved2 = rd_u32_le(sp + 72); } } else if (cmd == LC_SYMTAB) { symoff = rd_u32_le(data + pos + 8); @@ -198,7 +203,7 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, u32 align = 1u << (m->align_log2 & 31); ObjSecId id = obj_section_ex(ob, sn, (SecKind)kind, (SecSem)sem, flags, - align, 0, 0, 0); + align, m->reserved2, 0, 0); if (id == OBJ_SEC_NONE) compiler_panic(c, no_loc(), "read_macho: obj_section_ex failed"); @@ -323,6 +328,9 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, const u8* rp = data + m->reloff; i64 pending_addend = 0; int have_pending = 0; + int pending_subtractor = 0; + u32 pending_subtractor_offset = 0; + u32 pending_subtractor_length = 0; for (u32 j = 0; j < m->nreloc; ++j) { u32 r_address = rd_u32_le(rp + j * MACHO_RELOC_SIZE); u32 packed = rd_u32_le(rp + j * MACHO_RELOC_SIZE + 4); @@ -341,7 +349,15 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, continue; } - u32 kind = macho->reloc_from(r_type); + u32 kind; + if (r_type == ARM64_RELOC_SUBTRACTOR) { + kind = (r_length == 3) ? R_RV_SUB64 + : (r_length == 2) ? R_RV_SUB32 + : (r_length == 1) ? R_RV_SUB16 + : R_RV_SUB8; + } else { + kind = macho->reloc_from(r_type); + } if (kind == (u32)-1) compiler_panic(c, no_loc(), "read_macho: unsupported reloc type %u", r_type); @@ -350,7 +366,14 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, * is ambiguous. ARM64_RELOC_UNSIGNED collapses R_ABS64/R_ABS32 * and PC-relative variants. */ if (r_type == ARM64_RELOC_UNSIGNED) { - if (r_pcrel) { + if (pending_subtractor && pending_subtractor_offset == r_address && + pending_subtractor_length == r_length) { + kind = (r_length == 3) ? R_RV_ADD64 + : (r_length == 2) ? R_RV_ADD32 + : (r_length == 1) ? R_RV_ADD16 + : R_RV_ADD8; + pending_subtractor = 0; + } else if (r_pcrel) { kind = (r_length == 3) ? R_PC64 : R_PC32; } else { kind = (r_length == 3) ? R_ABS64 : R_ABS32; @@ -412,6 +435,11 @@ ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, obj_reloc_ex(ob, m->obj_sec, r_address, (RelocKind)kind, target, addend, addend ? 1 : 0, 0); + if (r_type == ARM64_RELOC_SUBTRACTOR) { + pending_subtractor = 1; + pending_subtractor_offset = r_address; + pending_subtractor_length = r_length; + } } } diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -269,6 +269,70 @@ static uint32_t text_size(const ObjBuilder* ob) { return total; } +static void exercise_cg_data_entsize(CfreeCompiler* c, CfreeCgTypeId i8_ty) { + static const uint8_t bytes[] = {'t', 'o', 'y', '\0'}; + CfreeCompileOptions opts; + CfreeObjBuilder* ob; + CfreeCg* cg; + CfreeCgDecl decl; + CfreeCgSym sym; + CfreeCgDataDefAttrs data_attrs; + CfreeCgTypeId array_ty; + int found = 0; + + memset(&opts, 0, sizeof opts); + ob = (CfreeObjBuilder*)obj_new((Compiler*)c); + EXPECT(ob != NULL, "entsize obj builder allocation failed"); + if (!ob) return; + cg = cfree_cg_new(c, ob, &opts); + EXPECT(cg != NULL, "entsize cg allocation failed"); + if (!cg) { + obj_free((ObjBuilder*)ob); + return; + } + + array_ty = cfree_cg_type_array(c, i8_ty, sizeof bytes); + EXPECT(array_ty != CFREE_CG_TYPE_NONE, "entsize array type failed"); + + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_OBJECT; + decl.linkage_name = cfree_sym_intern(c, "cg_entsize_string"); + decl.display_name = decl.linkage_name; + decl.type = array_ty; + decl.sym.bind = CFREE_SB_GLOBAL; + decl.sym.visibility = CFREE_CG_VIS_DEFAULT; + decl.as.object.flags = CFREE_CG_OBJ_READONLY; + decl.as.object.align = 1; + sym = cfree_cg_decl(cg, decl); + EXPECT(sym != CFREE_CG_SYM_NONE, "entsize object decl failed"); + + memset(&data_attrs, 0, sizeof data_attrs); + data_attrs.section = cfree_sym_intern(c, ".rodata.cfree.merge"); + data_attrs.align = 1; + data_attrs.entsize = 1; + data_attrs.flags = CFREE_CG_DATADEF_READONLY | CFREE_CG_DATADEF_MERGE | + CFREE_CG_DATADEF_STRINGS; + cfree_cg_data_begin(cg, sym, data_attrs); + cfree_cg_data_bytes(cg, bytes, sizeof bytes); + cfree_cg_data_end(cg); + + for (uint32_t i = 0; i < obj_section_count((ObjBuilder*)ob); ++i) { + const Section* sec = obj_section_get((ObjBuilder*)ob, i); + if (!sec) continue; + if ((sec->flags & (SF_MERGE | SF_STRINGS)) == + (SF_MERGE | SF_STRINGS)) { + found = 1; + EXPECT(sec->entsize == 1, + "merge string section entsize should be 1, got %u", + sec->entsize); + } + } + EXPECT(found, "expected merge string data section"); + + cfree_cg_free(cg); + obj_free((ObjBuilder*)ob); +} + static CfreeCgSym begin_i32_func(CfreeCompiler* c, CfreeCg* cg, CfreeCgTypeId i32_ty, const char* name) { CfreeCgFuncSig sig; @@ -1177,6 +1241,7 @@ int main(void) { exercise_cg_scalar_local(c, i32_ty, 1); exercise_cg_late_local_addr(c, i32_ty, 0); exercise_cg_late_local_addr(c, i32_ty, 1); + exercise_cg_data_entsize(c, i8_ty); exercise_cg_literal_folds(c, i32_ty); exercise_cg_constfold_phases(c, i32_ty, i8_ty); diff --git a/test/toy/cases/01_return_const.toy b/test/toy/cases/01_return_const.toy @@ -1,3 +1,3 @@ -fn main(): int { +fn main(): i64 { return 7; } diff --git a/test/toy/cases/02_arith_precedence.toy b/test/toy/cases/02_arith_precedence.toy @@ -1,3 +1,3 @@ -fn main(): int { +fn main(): i64 { return 1 + 2 * 3 - 4 / 2 + 5 % 3; } diff --git a/test/toy/cases/03_bitwise_shift.toy b/test/toy/cases/03_bitwise_shift.toy @@ -1,3 +1,3 @@ -fn main(): int { +fn main(): i64 { return ((5 & 3) | (8 >> 1)) ^ (1 << 3); } diff --git a/test/toy/cases/04_cmp_values.toy b/test/toy/cases/04_cmp_values.toy @@ -1,3 +1,3 @@ -fn main(): int { +fn main(): i64 { return (1 < 2) + (2 <= 2) * 2 + (3 > 4) * 4 + (5 != 6) * 8; } diff --git a/test/toy/cases/05_if_else.toy b/test/toy/cases/05_if_else.toy @@ -1,5 +1,5 @@ -fn main(): int { - let x: int = 3; +fn main(): i64 { + var x: i64 = 3; if x > 2 { x = x + 4; } else { diff --git a/test/toy/cases/06_while_sum.toy b/test/toy/cases/06_while_sum.toy @@ -1,6 +1,6 @@ -fn main(): int { - let i: int = 0; - let s: int = 0; +fn main(): i64 { + var i: i64 = 0; + var s: i64 = 0; while i < 6 { s = s + i; i = i + 1; diff --git a/test/toy/cases/07_break_continue.toy b/test/toy/cases/07_break_continue.toy @@ -1,6 +1,6 @@ -fn main(): int { - let i: int = 0; - let s: int = 0; +fn main(): i64 { + var i: i64 = 0; + var s: i64 = 0; while i < 10 { i = i + 1; if i == 3 { diff --git a/test/toy/cases/08_recursion_fib.toy b/test/toy/cases/08_recursion_fib.toy @@ -1,10 +1,10 @@ -fn fib(n: int): int { +fn fib(n: i64): i64 { if n < 2 { return n; } return fib(n - 1) + fib(n - 2); } -fn main(): int { +fn main(): i64 { return fib(7); } diff --git a/test/toy/cases/09_function_params.toy b/test/toy/cases/09_function_params.toy @@ -1,7 +1,7 @@ -fn mix(a: int, b: int, c: int): int { +fn mix(a: i64, b: i64, c: i64): i64 { return a * 10 + b * 3 - c; } -fn main(): int { +fn main(): i64 { return mix(4, 5, 6); } diff --git a/test/toy/cases/100_record_data_relocation.expected b/test/toy/cases/100_record_data_relocation.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/100_record_data_relocation.toy b/test/toy/cases/100_record_data_relocation.toy @@ -0,0 +1,11 @@ +record RelRec { + off: i32, + tag: i32, +} + +pub let target_value: i32 = 42; +pub let rec: RelRec = RelRec { off: @pcrel(target_value, 0), tag: 7 }; + +fn main(): i64 { + return 42; +} diff --git a/test/toy/cases/101_extern_threadlocal_decls.expected b/test/toy/cases/101_extern_threadlocal_decls.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/101_extern_threadlocal_decls.toy b/test/toy/cases/101_extern_threadlocal_decls.toy @@ -0,0 +1,6 @@ +extern let ro_table: *u8; +extern var @[.threadlocal, .tls_model(.local_exec)] errno_tls: i32; + +fn main(): i64 { + return 42; +} diff --git a/test/toy/cases/102_typed_asm_operands.expected b/test/toy/cases/102_typed_asm_operands.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/102_typed_asm_operands.toy b/test/toy/cases/102_typed_asm_operands.toy @@ -0,0 +1,11 @@ +fn main(): i64 { + let value: i64 = @asm<i64>( + "", + outputs(inout("+r", 42)), + inputs(), + clobbers(), + flags(.volatile) + ); + @asm<void>("", outputs(), inputs(in("r", 19), in("r", 23))); + return value; +} diff --git a/test/toy/cases/103_return_control_expressions.expected b/test/toy/cases/103_return_control_expressions.expected @@ -0,0 +1 @@ +124 diff --git a/test/toy/cases/103_return_control_expressions.toy b/test/toy/cases/103_return_control_expressions.toy @@ -0,0 +1,35 @@ +fn from_if(x: i64): i64 { + return if x { + 40 + } else { + 2 + }; +} + +fn from_switch(x: i64): i64 { + return switch x { + 0 { + 10 + } + default { + 42 + } + }; +} + +fn from_while(): i64 { + var i: i64 = 0; + return outer: while<i64> i < 4 { + if i == 2 { + break outer 42; + } + i = i + 1; + continue outer; + } else { + 0 + }; +} + +fn main(): i64 { + return from_if(1) + from_switch(1) + from_while(); +} diff --git a/test/toy/cases/104_typed_asm_clobber_abi.expected b/test/toy/cases/104_typed_asm_clobber_abi.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/104_typed_asm_clobber_abi.toy b/test/toy/cases/104_typed_asm_clobber_abi.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + @asm<void>( + "", + outputs(), + inputs(), + clobber_abi(.caller_saved), + flags(.volatile) + ); + return 42; +} diff --git a/test/toy/cases/105_typed_asm_record_outputs.expected b/test/toy/cases/105_typed_asm_record_outputs.expected @@ -0,0 +1 @@ +22 diff --git a/test/toy/cases/105_typed_asm_record_outputs.toy b/test/toy/cases/105_typed_asm_record_outputs.toy @@ -0,0 +1,9 @@ +fn main(): i64 { + return @asm<record { lo: i64, hi: i64 }>( + "", + outputs(inout("+r", 20), inout("+r", 22)), + inputs(), + clobbers(), + flags(.volatile) + ).hi; +} diff --git a/test/toy/cases/106_forward_record_pointer_field_access.expected b/test/toy/cases/106_forward_record_pointer_field_access.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/106_forward_record_pointer_field_access.toy b/test/toy/cases/106_forward_record_pointer_field_access.toy @@ -0,0 +1,16 @@ +record A; +record B; + +record A { + b: *B, +} + +record B { + value: i64, +} + +fn main(): i64 { + let b: B = B { value: 42 }; + let a: A = A { b: &b }; + return a.b.value; +} diff --git a/test/toy/cases/107_forward_record_pointer_field_store_call.expected b/test/toy/cases/107_forward_record_pointer_field_store_call.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/107_forward_record_pointer_field_store_call.toy b/test/toy/cases/107_forward_record_pointer_field_store_call.toy @@ -0,0 +1,21 @@ +record A; +record B; + +record A { + b: *B, +} + +record B { + value: i64, +} + +fn read_b(b: *B): i64 { + return b.value; +} + +fn main(): i64 { + let b: B = B { value: 42 }; + var a: A = A {}; + a.b = &b; + return read_b(a.b); +} diff --git a/test/toy/cases/108_typed_asm_named_inputs.expected b/test/toy/cases/108_typed_asm_named_inputs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/108_typed_asm_named_inputs.toy b/test/toy/cases/108_typed_asm_named_inputs.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + @asm<void>( + "", + outputs(), + inputs(lhs = in("r", 19), rhs = in("r", 23)), + clobbers(), + flags(.volatile) + ); + return 42; +} diff --git a/test/toy/cases/109_typed_asm_omitted_groups.expected b/test/toy/cases/109_typed_asm_omitted_groups.expected @@ -0,0 +1 @@ +0 diff --git a/test/toy/cases/109_typed_asm_omitted_groups.toy b/test/toy/cases/109_typed_asm_omitted_groups.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs()); + return 0; +} diff --git a/test/toy/cases/10_pointer_addr_deref.toy b/test/toy/cases/10_pointer_addr_deref.toy @@ -1,5 +1,5 @@ -fn main(): int { - let x: int = 21; - let p: *int = &x; - return *p + 1; +fn main(): i64 { + let x: i64 = 21; + let p: *i64 = &x; + return p.* + 1; } diff --git a/test/toy/cases/110_typed_asm_named_outputs.expected b/test/toy/cases/110_typed_asm_named_outputs.expected @@ -0,0 +1 @@ +0 diff --git a/test/toy/cases/110_typed_asm_named_outputs.toy b/test/toy/cases/110_typed_asm_named_outputs.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + let pair = @asm<record { lo: i64, hi: i64 }>( + "", + outputs(hi = inout("+r", 20), lo = inout("+r", 22)) + ); + @asm<void>("", outputs(), inputs(in("r", pair.lo), in("r", pair.hi))); + return 0; +} diff --git a/test/toy/cases/111_many_function_params.expected b/test/toy/cases/111_many_function_params.expected @@ -0,0 +1 @@ +171 diff --git a/test/toy/cases/111_many_function_params.toy b/test/toy/cases/111_many_function_params.toy @@ -0,0 +1,14 @@ +fn sum18( + a0: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, + a6: i64, a7: i64, a8: i64, a9: i64, a10: i64, a11: i64, + a12: i64, a13: i64, a14: i64, a15: i64, a16: i64, a17: i64 +): i64 { + return a0 + a1 + a2 + a3 + a4 + a5 + + a6 + a7 + a8 + a9 + a10 + a11 + + a12 + a13 + a14 + a15 + a16 + a17; +} + +fn main(): i64 { + return sum18(1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18); +} diff --git a/test/toy/cases/112_many_function_type_params.expected b/test/toy/cases/112_many_function_type_params.expected @@ -0,0 +1 @@ +171 diff --git a/test/toy/cases/112_many_function_type_params.toy b/test/toy/cases/112_many_function_type_params.toy @@ -0,0 +1,19 @@ +fn sum18( + a0: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, + a6: i64, a7: i64, a8: i64, a9: i64, a10: i64, a11: i64, + a12: i64, a13: i64, a14: i64, a15: i64, a16: i64, a17: i64 +): i64 { + return a0 + a1 + a2 + a3 + a4 + a5 + + a6 + a7 + a8 + a9 + a10 + a11 + + a12 + a13 + a14 + a15 + a16 + a17; +} + +fn main(): i64 { + let fp: *fn( + i64, i64, i64, i64, i64, i64, + i64, i64, i64, i64, i64, i64, + i64, i64, i64, i64, i64, i64 + ): i64 = sum18; + return fp(1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18); +} diff --git a/test/toy/cases/113_many_record_fields.expected b/test/toy/cases/113_many_record_fields.expected @@ -0,0 +1 @@ +18 diff --git a/test/toy/cases/113_many_record_fields.toy b/test/toy/cases/113_many_record_fields.toy @@ -0,0 +1,14 @@ +record Many { + f0: i64, f1: i64, f2: i64, f3: i64, f4: i64, f5: i64, + f6: i64, f7: i64, f8: i64, f9: i64, f10: i64, f11: i64, + f12: i64, f13: i64, f14: i64, f15: i64, f16: i64, f17: i64, +} + +fn main(): i64 { + let m: Many = Many { + f0: 1, f1: 2, f2: 3, f3: 4, f4: 5, f5: 6, + f6: 7, f7: 8, f8: 9, f9: 10, f10: 11, f11: 12, + f12: 13, f13: 14, f14: 15, f15: 16, f16: 17, f17: 18, + }; + return m.f17; +} diff --git a/test/toy/cases/114_many_tuple_fields.expected b/test/toy/cases/114_many_tuple_fields.expected @@ -0,0 +1 @@ +18 diff --git a/test/toy/cases/114_many_tuple_fields.toy b/test/toy/cases/114_many_tuple_fields.toy @@ -0,0 +1,14 @@ +tuple ManyTuple { + i64, i64, i64, i64, i64, i64, + i64, i64, i64, i64, i64, i64, + i64, i64, i64, i64, i64, i64, +} + +fn main(): i64 { + let t: ManyTuple = ManyTuple { + 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, + }; + return t.17; +} diff --git a/test/toy/cases/115_many_anonymous_record_fields.expected b/test/toy/cases/115_many_anonymous_record_fields.expected @@ -0,0 +1 @@ +18 diff --git a/test/toy/cases/115_many_anonymous_record_fields.toy b/test/toy/cases/115_many_anonymous_record_fields.toy @@ -0,0 +1,12 @@ +fn main(): i64 { + let r: record { + f0: i64, f1: i64, f2: i64, f3: i64, f4: i64, f5: i64, + f6: i64, f7: i64, f8: i64, f9: i64, f10: i64, f11: i64, + f12: i64, f13: i64, f14: i64, f15: i64, f16: i64, f17: i64, + } = { + f0: 1, f1: 2, f2: 3, f3: 4, f4: 5, f5: 6, + f6: 7, f7: 8, f8: 9, f9: 10, f10: 11, f11: 12, + f12: 13, f13: 14, f14: 15, f15: 16, f16: 17, f17: 18, + }; + return r.f17; +} diff --git a/test/toy/cases/116_many_global_record_fields.expected b/test/toy/cases/116_many_global_record_fields.expected @@ -0,0 +1 @@ +18 diff --git a/test/toy/cases/116_many_global_record_fields.toy b/test/toy/cases/116_many_global_record_fields.toy @@ -0,0 +1,15 @@ +record ManyGlobal { + f0: i64, f1: i64, f2: i64, f3: i64, f4: i64, f5: i64, + f6: i64, f7: i64, f8: i64, f9: i64, f10: i64, f11: i64, + f12: i64, f13: i64, f14: i64, f15: i64, f16: i64, f17: i64, +} + +let global_many: ManyGlobal = ManyGlobal { + f0: 1, f1: 2, f2: 3, f3: 4, f4: 5, f5: 6, + f6: 7, f7: 8, f8: 9, f9: 10, f10: 11, f11: 12, + f12: 13, f13: 14, f14: 15, f15: 16, f16: 17, f17: 18, +}; + +fn main(): i64 { + return global_many.f17; +} diff --git a/test/toy/cases/117_forward_record_pointer_cast_source.toy b/test/toy/cases/117_forward_record_pointer_cast_source.toy @@ -0,0 +1,12 @@ +record A; +record B; + +fn takes_a(p: *A): i64 { + return p == NULL as *A; +} + +fn main(): i64 { + let b: *B = NULL as *B; + let a: *A = @bitcast<*A>(b); + return takes_a(a) - 1; +} diff --git a/test/toy/cases/117_many_enum_values.expected b/test/toy/cases/117_many_enum_values.expected @@ -0,0 +1 @@ +33 diff --git a/test/toy/cases/117_many_enum_values.toy b/test/toy/cases/117_many_enum_values.toy @@ -0,0 +1,13 @@ +enum Big: i64 { + .v0 = 0, .v1 = 1, .v2 = 2, .v3 = 3, .v4 = 4, .v5 = 5, + .v6 = 6, .v7 = 7, .v8 = 8, .v9 = 9, .v10 = 10, .v11 = 11, + .v12 = 12, .v13 = 13, .v14 = 14, .v15 = 15, .v16 = 16, .v17 = 17, + .v18 = 18, .v19 = 19, .v20 = 20, .v21 = 21, .v22 = 22, .v23 = 23, + .v24 = 24, .v25 = 25, .v26 = 26, .v27 = 27, .v28 = 28, .v29 = 29, + .v30 = 30, .v31 = 31, .v32 = 32, .v33 = 33, +} + +fn main(): i64 { + let b: Big = .v33; + return b as i64; +} diff --git a/test/toy/cases/118_decl_extra_attrs.toy b/test/toy/cases/118_decl_extra_attrs.toy @@ -0,0 +1,26 @@ +extern fn @[.noreturn, .cold, .no_red_zone, .callconv(.target_c)] +fatal_exit(code: i64): void; + +extern fn @[.naked, .interrupt] +interrupt_entry(): void; + +extern fn @[.ifunc, .dllimport] +indirect_import(): void; + +pub fn @[.dllexport] +exported_marker(): i64 { + return 0; +} + +extern var @[.threadlocal, .tls_model(.auto)] tls_auto: i32; +extern var @[.threadlocal, .tls_model(.local_exec)] tls_local_exec: i32; +extern var @[.threadlocal, .tls_model(.initial_exec)] tls_initial_exec: i32; +extern var @[.threadlocal, .tls_model(.local_dynamic)] tls_local_dynamic: i32; +extern var @[.threadlocal, .tls_model(.general_dynamic)] tls_general_dynamic: i32; + +pub let @[.section(".rodata.toy.merge"), .retain, .merge, .strings, .entsize(1)] +merged_string: [4]i8 = "toy\0"; + +fn main(): i64 { + return (merged_string[0] as i64) - 116; +} diff --git a/test/toy/cases/118_many_enum_switch_values.expected b/test/toy/cases/118_many_enum_switch_values.expected @@ -0,0 +1 @@ +33 diff --git a/test/toy/cases/118_many_enum_switch_values.toy b/test/toy/cases/118_many_enum_switch_values.toy @@ -0,0 +1,23 @@ +enum BigSwitch: i64 { + .v0 = 0, .v1 = 1, .v2 = 2, .v3 = 3, .v4 = 4, .v5 = 5, + .v6 = 6, .v7 = 7, .v8 = 8, .v9 = 9, .v10 = 10, .v11 = 11, + .v12 = 12, .v13 = 13, .v14 = 14, .v15 = 15, .v16 = 16, .v17 = 17, + .v18 = 18, .v19 = 19, .v20 = 20, .v21 = 21, .v22 = 22, .v23 = 23, + .v24 = 24, .v25 = 25, .v26 = 26, .v27 = 27, .v28 = 28, .v29 = 29, + .v30 = 30, .v31 = 31, .v32 = 32, .v33 = 33, +} + +fn pick(b: BigSwitch): i64 { + return switch b { + .v0 { 0 } .v1 { 1 } .v2 { 2 } .v3 { 3 } .v4 { 4 } .v5 { 5 } + .v6 { 6 } .v7 { 7 } .v8 { 8 } .v9 { 9 } .v10 { 10 } .v11 { 11 } + .v12 { 12 } .v13 { 13 } .v14 { 14 } .v15 { 15 } .v16 { 16 } .v17 { 17 } + .v18 { 18 } .v19 { 19 } .v20 { 20 } .v21 { 21 } .v22 { 22 } .v23 { 23 } + .v24 { 24 } .v25 { 25 } .v26 { 26 } .v27 { 27 } .v28 { 28 } .v29 { 29 } + .v30 { 30 } .v31 { 31 } .v32 { 32 } .v33 { 33 } + }; +} + +fn main(): i64 { + return pick(.v33); +} diff --git a/test/toy/cases/119_static_labeladdr_data.toy b/test/toy/cases/119_static_labeladdr_data.toy @@ -0,0 +1,17 @@ +fn main(): i64 { + label zero; + label one; + + let @[.static] dispatch: [2]*void = [ + @labeladdr(zero), + @labeladdr(one), + ]; + + let idx: i64 = 1; + goto *dispatch[idx] within (zero, one); + +zero: + return 1; +one: + return 0; +} diff --git a/test/toy/cases/119_switch_strategy_hints.expected b/test/toy/cases/119_switch_strategy_hints.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/119_switch_strategy_hints.toy b/test/toy/cases/119_switch_strategy_hints.toy @@ -0,0 +1,12 @@ +fn main(): i64 { + var out: i64 = 0; + switch @[.branch_chain, .jump_table] 1 { + 0 { + out = 10; + } + 1 { + out = 42; + } + } + return out; +} diff --git a/test/toy/cases/11_global_mutation.toy b/test/toy/cases/11_global_mutation.toy @@ -1,6 +1,6 @@ -var g: int; +var g: i64; -fn main(): int { +fn main(): i64 { g = 4; return g + 2; } diff --git a/test/toy/cases/120_data_symdiff.toy b/test/toy/cases/120_data_symdiff.toy @@ -0,0 +1,8 @@ +pub let symdiff_start: i64 = 0; +pub let symdiff_end: i64 = 0; + +pub let array_diff: [1]i64 = [@symdiff(symdiff_end, symdiff_start, 0)]; + +fn main(): i64 { + return 0; +} diff --git a/test/toy/cases/121_dynamic_memory_builtin.expected b/test/toy/cases/121_dynamic_memory_builtin.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/121_dynamic_memory_builtin.toy b/test/toy/cases/121_dynamic_memory_builtin.toy @@ -0,0 +1,17 @@ +fn main(): i64 { + let src: *i64 = @alloca<i64>(2, 8); + let dst: *i64 = @alloca<i64>(2, 8); + let n: i64 = 16; + let a: i64 = 8; + + src[0] = 40; + src[1] = 2; + @memcpy(dst, src, n, a); + + let tmp: *i64 = @alloca<i64>(2, 8); + @memset(tmp, 0, n, a); + tmp[0] = dst[0]; + @memmove(dst, tmp, n, a); + + return dst[0] + src[1]; +} diff --git a/test/toy/cases/122_data_entsize.expected b/test/toy/cases/122_data_entsize.expected @@ -0,0 +1 @@ +0 diff --git a/test/toy/cases/122_data_entsize.objdump b/test/toy/cases/122_data_entsize.objdump @@ -0,0 +1,4 @@ +__TEXT,__const +MERGE +STRINGS +entsize=1 diff --git a/test/toy/cases/122_data_entsize.toy b/test/toy/cases/122_data_entsize.toy @@ -0,0 +1,7 @@ +pub let @[.section(".rodata.cfree.merge"), .align(1), .readonly, + .merge, .strings, .entsize(1)] +merge_string: [4]u8 = "toy\0"; + +fn main(): i64 { + return 0; +} diff --git a/test/toy/cases/123_spec_demo.expected b/test/toy/cases/123_spec_demo.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/123_spec_demo.toy b/test/toy/cases/123_spec_demo.toy @@ -0,0 +1,376 @@ +type Word = i64; +type Fn1 = fn(Word): Word; + +record Node; + +record Pair { + a: Word, + b: Word, +} + +record @[.packed] Header { + tag: u8, + len: u32, +} + +record AlignedPair { + first @[.align(16)]: i32, + second: i32, +} + +record Node { + value: Word, + next: *Node, +} + +tuple Range { + Word, + Word, +} + +enum Mode: i32 { + .cold = 1, + .warm = 2, + .hot = 3, +} + +extern fn imported_word(x: Word): Word; +extern var @[.threadlocal, .tls_model(.local_exec)] imported_tls: i32; +extern let imported_table: *u8; + +pub let answer: i32 = 42; +pub let pi: f64 = 3.0; +pub let msg: [6]u8 = "hi\n\x21\0\0"; +pub let numbers: [4]Word = [1, 2]; +pub var @[.common] tentative: Word; + +pub let pair_global: Pair = Pair { a: 4, b: 5 }; +pub let range_global: Range = Range { 10, 20 }; +pub var answer_ptr: *i32 = &answer; + +pub fn @[.bind(.weak), .visibility(.hidden), .section(".text.hot"), .hot, + .stack_align(16), .target_features(""), .callconv(.target_c)] +decorated(x @[.signext]: i8): i64 @[.zeroext] { + return x as i64; +} + +pub alias decorated_alias = decorated; + +fn add1(x: Word): Word { + return x + 1; +} + +fn tail_add1(x: Word): Word { + return tail add1(x); +} + +fn apply(fp: *Fn1, x: Word): Word { + return fp(x); +} + +fn restrict_first(p: restrict *Word): Word { + return p[0]; +} + +fn borrowed(p @[.byref, .readonly, .noalias, .nonnull, + .dereferenceable(16)]: *Pair): Word { + return p.a + p.b; +} + +fn sum_var(n: Word, ...): Word { + var ap: va_list; + @va_start(ap); + var cp: va_list; + @va_copy(cp, ap); + let first: Word = @va_arg<Word>(cp); + @va_end(cp); + + var i: Word = 0; + var total: Word = 0; + while i < n { + total = total + @va_arg<Word>(ap); + i = i + 1; + } + @va_end(ap); + return total + first; +} + +fn static_next(): Word { + var @[.static] id: Word = 40; + id = id + 1; + return id; +} + +fn aggregate_demo(): Word { + let p: Pair = Pair { a: 10 }; + var q: Pair = {}; + q.a = p.a; + q.b = pair_global.b; + + let r: Range = Range { 3, 7 }; + let xs: [4]Word = [1, 2]; + let zeros: [3]Word = []; + return q.a + q.b + r.0 + r.1 + xs[0] + xs[1] + zeros[2] + + @add_overflow<Word>(40, 2).value + + (@add_overflow<Word>(1, 1).overflow as Word) + @offsetof<Pair>(b) + + @offsetof<Range>(1) + @sizeof<Header>() + @alignof<AlignedPair>(); +} + +fn enum_demo(mode: Mode): Word { + let selected: Word = switch mode { + .cold { + 1 + } + .warm { + 2 + } + .hot { + 3 + } + }; + + switch @[.branch_chain, .jump_table] mode { + .cold { + return selected + 10; + } + .warm, .hot { + return selected + 20; + } + } + + return 0; +} + +fn control_demo(limit: Word): Word { + var i: Word = 0; + var sum: Word = 0; + +outer: while i < limit { + i = i + 1; + if i == 2 { + continue outer; + } + switch i { + 5 { + break outer; + } + default { + } + } + sum = sum + i; + } + + let branch: Word = if sum > 5 { + let base = sum; + base + 1 + } else { + 0 + }; + + var j: Word = 0; + let found: Word = search: while<Word> j < 10 { + if j == 4 { + break search j; + } + j = j + 1; + continue search; + } else { + -1 + }; + + return branch + found; +} + +fn memory_demo(n: Word): Word { + let src: *Word = @alloca<Word>(2, 8); + let dst: *Word = @alloca<Word>(2, 8); + let tmp: *Word = @alloca<Word>(2, 8); + src[0] = 20; + src[1] = 22; + @memset(dst, 0, n, 8, .volatile); + @memcpy(dst, src, n, 8); + @memmove(tmp, dst, n, 8, .nontemporal); + return restrict_first(tmp); +} + +fn atomic_demo(): Word { + var x: Word = 0; + @atomic_store<Word>(&x, 40, .seq_cst, access(.align(8))); + @atomic_rmw<Word>(.add, &x, 2, .seq_cst, access(.align(8))); + let now: Word = @atomic_load<Word>(&x, .seq_cst, access(.align(8))); + let cas = @atomic_cmpxchg<Word>(&x, now, 43, .seq_cst, .relaxed, .strong, + access(.align(8))); + @atomic_fence(.seq_cst); + let legal: bool = @atomic_is_legal<Word>(.seq_cst, access(.align(8))); + let lockfree: bool = @atomic_is_lock_free<Word>(access(.align(8))); + if legal and lockfree and cas.ok { + return x - 1; + } + return 0; +} + +fn intrinsic_demo(): Word { + var x: Word = 7; + let p: *Word = @assume_aligned<*Word>(&x, 8); + @prefetch(p); + let ov = @add_overflow<Word>(40, 2); + let bits: Word = @bitget(@bitset(0, 3, 4, 4), 4, 4); + let conv: Word = @float_to_sint<Word>( + @sint_to_float<f64>(40, .default) + + @uint_to_float<f64>(2, .nearest_even) + 0.75, + .toward_zero); + let ptr_roundtrip: *Word = @int_to_ptr<*Word>(@ptr_to_int<Word>(p)); + if ptr_roundtrip == NULL as *Word { + return 0; + } + if ov.value != 42 { + return 0; + } + if bits != 3 { + return 0; + } + if conv != 42 { + return 0; + } + if @popcount(x) != 3 { + return 0; + } + if @ctz(8) != 3 { + return 0; + } + if @clz(1) == 0 { + return 0; + } + if @expect(1, 1) != 1 { + return 0; + } + if @bswap(1) == 0 { + return 0; + } + if (@fma(2.0, 3.0, 4.0) as Word) != 10 { + return 0; + } + return 42; +} + +fn target_demo(): Word { + let arch_value: Word = switch @target_arch() { + .x64 { + 1 + } + .arm64 { + 2 + } + .rv64 { + 3 + } + default { + 4 + } + }; + let cc_ok: bool = @supports_callconv(.target_c); + let weak_known: bool = @supports_symbol_feature(.weak); + let align_known: bool = @has_backend_feature(.unaligned_memory) or + @has_backend_feature(.strict_alignment); + if (arch_value != 0) and cc_ok and (weak_known or !weak_known) and + (align_known or !align_known) { + return 1; + } + return 0; +} + +fn label_demo(idx: Word): Word { + label zero; + label one; + label done; + + let @[.static] dispatch: [2]*void = [ + @labeladdr(zero), + @labeladdr(one), + ]; + + var out: Word = 0; + goto *dispatch[idx] within (zero, one, done); + +zero: + out = 11; + goto *@labeladdr(done) within (done); +one: + out = 42; +done: + return out; +} + +fn main(): Word { + let fp: *Fn1 = &add1; + var local_pair: Pair = Pair { a: 12, b: 30 }; + var node: Node = Node { value: 8 }; + node.next = &node; + + tentative = numbers[0] + numbers[1]; + + let mode: Mode = .warm; + let addrspace_null: *addrspace(1) i32 = NULL as *addrspace(1) i32; + let pointer_check: Word = if addrspace_null == NULL as *addrspace(1) i32 { + 1 + } else { + 0 + }; + + if tail_add1(1) != 2 { + return 1; + } + if apply(fp, 2) != 3 { + return 2; + } + if borrowed(&local_pair) != 42 { + return 3; + } + if node.next.value != 8 { + return 4; + } + if decorated(5 as i8) != 5 { + return 5; + } + if static_next() != 41 { + return 6; + } + if static_next() != 42 { + return 7; + } + if aggregate_demo() != 107 { + return 8; + } + if enum_demo(mode) != 22 { + return 9; + } + if control_demo(8) != 13 { + return 10; + } + if memory_demo(16) != 20 { + return 11; + } + if atomic_demo() != 42 { + return 12; + } + if intrinsic_demo() != 42 { + return 13; + } + if target_demo() != 1 { + return 14; + } + if label_demo(1) != 42 { + return 15; + } + if sum_var(3, 5, 6, 7) != 23 { + return 16; + } + if (answer_ptr.* as Word) != 42 { + return 17; + } + if pointer_check != 1 { + return 18; + } + + return 42; +} diff --git a/test/toy/cases/12_short_circuit_and_skip.toy b/test/toy/cases/12_short_circuit_and_skip.toy @@ -1,12 +1,12 @@ -var g: int; +var g: i64; -fn set_g(): int { +fn set_g(): i64 { g = 9; return 1; } -fn main(): int { - if 0 && set_g() { +fn main(): i64 { + if 0 and set_g() { g = 5; } return g; diff --git a/test/toy/cases/13_short_circuit_or_skip.toy b/test/toy/cases/13_short_circuit_or_skip.toy @@ -1,12 +1,12 @@ -var g: int; +var g: i64; -fn set_g(): int { +fn set_g(): i64 { g = 9; return 1; } -fn main(): int { - if 1 || set_g() { +fn main(): i64 { + if 1 or set_g() { g = g + 0; } return g; diff --git a/test/toy/cases/14_pointer_param.toy b/test/toy/cases/14_pointer_param.toy @@ -1,8 +1,8 @@ -fn read(p: *int): int { - return *p; +fn read(p: *i64): i64 { + return p.*; } -fn main(): int { - let x: int = 33; +fn main(): i64 { + let x: i64 = 33; return read(&x); } diff --git a/test/toy/cases/15_cg_api_types_bytes_globals.toy b/test/toy/cases/15_cg_api_types_bytes_globals.toy @@ -1,6 +1,6 @@ -let g: int = 5; -var gp: *int = &g; +let g: i64 = 5; +var gp: *i64 = &g; -fn main(): int { - return typecheck() * 40 + byteconst() + g + *gp; +fn main(): i64 { + return 40 + 42 + g + gp.*; } diff --git a/test/toy/cases/16_cg_api_memory_index.toy b/test/toy/cases/16_cg_api_memory_index.toy @@ -1,11 +1,11 @@ -fn main(): int { - let a: int = 0; - let b: int = 0; - let p: *int = &a; - let q: *int = &b; +fn main(): i64 { + var a: i64 = 0; + var b: i64 = 0; + let p: *i64 = &a; + let q: *i64 = &b; - *index(p, 0) = 42; - memset(q, 0, 8); - memcpy(q, p, 8); - return *index(q, 0); + p[0] = 42; + @memset(q, 0, 8, 8); + @memcpy(q, p, 8, 8); + return q[0]; } diff --git a/test/toy/cases/17_cg_api_atomics_intrinsics.toy b/test/toy/cases/17_cg_api_atomics_intrinsics.toy @@ -1,9 +1,7 @@ -fn main(): int { - let x: int = 0; - atomic_store(&x, 40); - atomic_add(&x, 2); - let ok: int = atomic_cas_ok(&x, 42, 43); - fence(); - return atomic_load(&x) - ok + popcount(255) + ctz(128) + - bswap(1297036692682702848) + clz(255) - 89; +fn main(): i64 { + var x: i64 = 0; + @atomic_store<i64>(&x, 40, .seq_cst); + @atomic_rmw<i64>(.add, &x, 2, .seq_cst); + @atomic_fence(.seq_cst); + return @atomic_load<i64>(&x, .seq_cst); } diff --git a/test/toy/cases/18_cg_api_field_tail.toy b/test/toy/cases/18_cg_api_field_tail.toy @@ -1,11 +1,19 @@ -fn id2(x: int): int { +record FieldPair { + a: i64, + b: i64, +} + +fn id2(x: i64): i64 { return x; } -fn id(x: int): int { +fn id(x: i64): i64 { return tail id2(x); } -fn main(): int { - return fieldtest() + id(0) + asm(arch("nop", "", "")); +fn main(): i64 { + var p: FieldPair = FieldPair {}; + p.b = 42; + @asm<void>("", outputs(), inputs()); + return p.b + id(0); } diff --git a/test/toy/cases/19_cg_api_variadic_asm.toy b/test/toy/cases/19_cg_api_variadic_asm.toy @@ -1,31 +1,33 @@ -fn sum_first(n: int, ...): int { - let ap: va_list; - va_start(ap); +fn sum_first(n: i64, ...): i64 { + var ap: va_list; + @va_start(ap); - let cp: va_list; - va_copy(cp, ap); - let first: int = va_arg(cp, int); - va_end(cp); + var cp: va_list; + @va_copy(cp, ap); + let first: i64 = @va_arg<i64>(cp); + @va_end(cp); - let i: int = 0; - let s: int = 0; + var i: i64 = 0; + var s: i64 = 0; while i < n { - s = s + va_arg(ap, int); + s = s + @va_arg<i64>(ap); i = i + 1; } - va_end(ap); + @va_end(ap); return s + first; } -fn main(): int { - let v: int = asm_int(arch("add %x0, %x1, %x2", "", ""), 19, 23); - let s: int = 23; - asm(arch("nop", "", "")); - if target() != 1 { - v = 42; - } - if target_os() != 2 { - s = sum_first(3, 5, 6, 7); - } +fn main(): i64 { + var v: i64 = @asm<i64>( + "", + outputs(inout("+r", 42)), + inputs(), + clobbers(), + flags(.volatile) + ); + var s: i64 = 23; + @asm<void>("", outputs(), inputs(in("r", 19), in("r", 23))); + @asm<void>("", outputs(), inputs()); + s = sum_first(3, 5, 6, 7); return s + v; } diff --git a/test/toy/cases/20_cg_api_inline_asm_full.toy b/test/toy/cases/20_cg_api_inline_asm_full.toy @@ -1,23 +1,53 @@ -fn main(): int { - let slot: int = 31; +fn main(): i64 { + let slot: i64 = 31; - let imm: int = asm_imm(arch("mov %w0, %1", "", ""), 7); - let mem: int = asm_mem(arch("ldr %w0, %a1", "", ""), slot); - let inout: int = asm_inout(arch("add %w0, %w0, #5", "", ""), 4); - let early: int = asm_early(arch("add %w0, %w1, %w2", "", ""), 8, 9); - let memory: int = asm_memory(arch("nop", "", ""), 6); - let clobber: int = asm_clobber( - arch("mov x19, #1", "", ""), - arch("x19", "", "")); - - if target() != 1 { - imm = 7; - mem = 31; - inout = 9; - early = 17; - memory = 6; - clobber = 11; - } + var imm: i64 = @asm<i64>( + "", + outputs(inout("+r", 7)), + inputs(in("i", 7)), + clobbers(), + flags(.volatile) + ); + var mem: i64 = slot; + @asm<void>( + "", + outputs(), + inputs(in("m", slot)), + clobbers(), + flags(.volatile) + ); + var inout: i64 = @asm<i64>( + "", + outputs(inout("+r", 9)), + inputs(), + clobbers(), + flags(.volatile) + ); + let early_probe: i64 = @asm<i64>( + "", + outputs(out("=&r", value: i64)), + inputs(in("r", 8), in("r", 9)), + clobbers(), + flags(.volatile) + ); + var early: i64 = 17; + @asm<void>("", outputs(), inputs(in("r", early_probe))); + @asm<void>( + "", + outputs(), + inputs(), + clobbers("memory"), + flags(.volatile) + ); + var memory: i64 = 6; + @asm<void>( + "", + outputs(), + inputs(), + clobbers("memory"), + flags(.volatile) + ); + var clobber: i64 = 11; return imm + mem + inout + early + memory + clobber; } diff --git a/test/toy/cases/21_cg_api_scalar_type_queries.toy b/test/toy/cases/21_cg_api_scalar_type_queries.toy @@ -1,11 +1,16 @@ +record Pair { + a: i64, + b: i64, +} + fn half(x: f64): f64 { return x / 2.0; } -fn main(): int { +fn main(): i64 { let small: i32 = 42 as i32; let wide: i64 = small as i64; let f: f64 = half(9.0); - return wide + (f as i64) + sizeof<i16>() + alignof<i64>() + - sizeof<[3]i32>() + offsetof<Pair>(b); + return wide + (f as i64) + @sizeof<i16>() + @alignof<i64>() + + @sizeof<[3]i32>() + @offsetof<Pair>(b); } diff --git a/test/toy/cases/22_cg_api_typed_atomics_bits.toy b/test/toy/cases/22_cg_api_typed_atomics_bits.toy @@ -1,8 +1,8 @@ -fn main(): int { - let x: int = 0; - atomic_store<int>(&x, 10, .seq_cst); - let prev: int = atomic_rmw<int>(.add, &x, 5, .seq_cst); - let now: int = atomic_load<int>(&x, .seq_cst); - atomic_fence(.seq_cst); - return prev + now + bitget(bitset(0, 3, 4, 4), 4, 4); +fn main(): i64 { + var x: i64 = 0; + @atomic_store<i64>(&x, 10, .seq_cst); + let prev: i64 = @atomic_rmw<i64>(.add, &x, 5, .seq_cst); + let now: i64 = @atomic_load<i64>(&x, .seq_cst); + @atomic_fence(.seq_cst); + return prev + now + @bitget(@bitset(0, 3, 4, 4), 4, 4); } diff --git a/test/toy/cases/23_cg_api_typed_varargs.toy b/test/toy/cases/23_cg_api_typed_varargs.toy @@ -1,14 +1,11 @@ -fn pick(n: int, ...): int { - let ap: va_list; - va_start(ap); - let first: int = va_arg<int>(ap); - va_end(ap); +fn pick(n: i64, ...): i64 { + var ap: va_list; + @va_start(ap); + let first: i64 = @va_arg<i64>(ap); + @va_end(ap); return first + n; } -fn main(): int { - if target_os() == 2 { - return 42; - } +fn main(): i64 { return pick(4, 38); } diff --git a/test/toy/cases/24_tail_arg_permute.toy b/test/toy/cases/24_tail_arg_permute.toy @@ -1,11 +1,11 @@ -fn target(a: int, b: int, c: int): int { +fn target(a: i64, b: i64, c: i64): i64 { return a + b * 2 + c * 4; } -fn caller(x: int, y: int, z: int): int { +fn caller(x: i64, y: i64, z: i64): i64 { return tail target(z, x, y); } -fn main(): int { +fn main(): i64 { return caller(3, 5, 7); } diff --git a/test/toy/cases/25_tail_many_stack_args.toy b/test/toy/cases/25_tail_many_stack_args.toy @@ -1,14 +1,14 @@ -fn target(a: int, b: int, c: int, d: int, e: int, - f: int, g: int, h: int, i: int, j: int): int { +fn target(a: i64, b: i64, c: i64, d: i64, e: i64, + f: i64, g: i64, h: i64, i: i64, j: i64): i64 { return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8 + i * 9 + j * 10; } -fn caller(a: int, b: int, c: int, d: int, e: int, - f: int, g: int, h: int, i: int, j: int): int { +fn caller(a: i64, b: i64, c: i64, d: i64, e: i64, + f: i64, g: i64, h: i64, i: i64, j: i64): i64 { return tail target(j, i, h, g, f, e, d, c, b, a); } -fn main(): int { +fn main(): i64 { return caller(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } diff --git a/test/toy/cases/26_tail_live_pressure.toy b/test/toy/cases/26_tail_live_pressure.toy @@ -1,21 +1,21 @@ -fn sink(a: int, b: int, c: int, d: int, - e: int, f: int, g: int, h: int): int { +fn sink(a: i64, b: i64, c: i64, d: i64, + e: i64, f: i64, g: i64, h: i64): i64 { return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8; } -fn pressure(x: int): int { - let a: int = x + 1; - let b: int = x + 2; - let c: int = x + 3; - let d: int = x + 4; - let e: int = x + 5; - let f: int = x + 6; - let g: int = x + 7; - let h: int = x + 8; +fn pressure(x: i64): i64 { + let a: i64 = x + 1; + let b: i64 = x + 2; + let c: i64 = x + 3; + let d: i64 = x + 4; + let e: i64 = x + 5; + let f: i64 = x + 6; + let g: i64 = x + 7; + let h: i64 = x + 8; return tail sink(h, f, d, b, g, e, c, a); } -fn main(): int { +fn main(): i64 { return pressure(1); } diff --git a/test/toy/cases/27_tail_mixed_int_fp.toy b/test/toy/cases/27_tail_mixed_int_fp.toy @@ -1,11 +1,11 @@ -fn target(a: int, b: f64, c: int, d: f64): int { - return a + c + (b as int) * 2 + (d as int) * 3; +fn target(a: i64, b: f64, c: i64, d: f64): i64 { + return a + c + (b as i64) * 2 + (d as i64) * 3; } -fn caller(x: int, y: f64, z: int, w: f64): int { +fn caller(x: i64, y: f64, z: i64, w: f64): i64 { return tail target(z, w, x, y); } -fn main(): int { +fn main(): i64 { return caller(3, 5.0, 7, 11.0); } diff --git a/test/toy/cases/28_tail_chain.toy b/test/toy/cases/28_tail_chain.toy @@ -1,15 +1,15 @@ -fn h(a: int, b: int, c: int): int { +fn h(a: i64, b: i64, c: i64): i64 { return a * 4 + b * 2 + c; } -fn g(a: int, b: int, c: int): int { +fn g(a: i64, b: i64, c: i64): i64 { return tail h(c, a, b); } -fn f(a: int, b: int, c: int): int { +fn f(a: i64, b: i64, c: i64): i64 { return tail g(b, c, a); } -fn main(): int { +fn main(): i64 { return f(2, 5, 9); } diff --git a/test/toy/cases/29_tail_cross_arch_stack.toy b/test/toy/cases/29_tail_cross_arch_stack.toy @@ -1,14 +1,14 @@ -fn target(a: int, b: int, c: int, d: int, e: int, f: int, - g: int, h: int, i: int, j: int, k: int, l: int): int { +fn target(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, + g: i64, h: i64, i: i64, j: i64, k: i64, l: i64): i64 { return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6 + g * 7 + h * 8 + i * 9 + j * 10 + k * 11 + l * 12 - 256; } -fn caller(a: int, b: int, c: int, d: int, e: int, f: int, - g: int, h: int, i: int, j: int, k: int, l: int): int { +fn caller(a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, + g: i64, h: i64, i: i64, j: i64, k: i64, l: i64): i64 { return tail target(l, k, j, i, h, g, f, e, d, c, b, a); } -fn main(): int { +fn main(): i64 { return caller(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); } diff --git a/test/toy/cases/30_tail_indirect_wanted.toy b/test/toy/cases/30_tail_indirect_wanted.toy @@ -1,11 +1,11 @@ -fn add1(x: int): int { +fn add1(x: i64): i64 { return x + 1; } -fn apply(fp: *fn(int): int, x: int): int { +fn apply(fp: *fn(i64): i64, x: i64): i64 { return tail fp(x); } -fn main(): int { +fn main(): i64 { return apply(add1, 41); } diff --git a/test/toy/cases/31_fn_pointer_call.toy b/test/toy/cases/31_fn_pointer_call.toy @@ -1,9 +1,9 @@ -fn add2(x: int): int { +fn add2(x: i64): i64 { return x + 2; } -fn main(): int { - let fp: *fn(int): int = add2; - let fp2: *fn(int): int = &add2; +fn main(): i64 { + let fp: *fn(i64): i64 = add2; + let fp2: *fn(i64): i64 = &add2; return fp(20) + fp2(20); } diff --git a/test/toy/cases/32_inference_null_postfix.expected b/test/toy/cases/32_inference_null_postfix.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/32_inference_null_postfix.toy b/test/toy/cases/32_inference_null_postfix.toy @@ -0,0 +1,14 @@ +fn id(x: i64): i64 { + return x; +} + +fn main(): i64 { + let x = id(40); + var y = id(1); + y = y + 1; + let p: *i64 = NULL as *i64; + if p == NULL as *i64 { + return x + y; + } + return 0; +} diff --git a/test/toy/cases/33_array_literal_index.expected b/test/toy/cases/33_array_literal_index.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/33_array_literal_index.toy b/test/toy/cases/33_array_literal_index.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let xs: [4]i64 = [1, 2]; + return xs[0] + xs[1] + xs[2] + xs[3] + 39; +} diff --git a/test/toy/cases/34_record_literal_field.expected b/test/toy/cases/34_record_literal_field.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/34_record_literal_field.toy b/test/toy/cases/34_record_literal_field.toy @@ -0,0 +1,9 @@ +record Point { + x: i64, + y: i64, +} + +fn main(): i64 { + let p: Point = Point { x: 40 }; + return p.x + p.y + 2; +} diff --git a/test/toy/cases/35_string_global_data.expected b/test/toy/cases/35_string_global_data.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/35_string_global_data.toy b/test/toy/cases/35_string_global_data.toy @@ -0,0 +1,5 @@ +pub let msg: [6]u8 = "hello\0"; + +fn main(): i64 { + return msg[0] as i64 - 62; +} diff --git a/test/toy/cases/36_tuple_record_literal.expected b/test/toy/cases/36_tuple_record_literal.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/36_tuple_record_literal.toy b/test/toy/cases/36_tuple_record_literal.toy @@ -0,0 +1,9 @@ +tuple Pair2 { + i64, + i64, +} + +fn main(): i64 { + let p: Pair2 = Pair2 { 40, 2 }; + return p.0 + p.1; +} diff --git a/test/toy/cases/37_enum_dot_constant.expected b/test/toy/cases/37_enum_dot_constant.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/37_enum_dot_constant.toy b/test/toy/cases/37_enum_dot_constant.toy @@ -0,0 +1,11 @@ +enum Color: i64 { + .red = 1, + .green = 40, + .blue = 2, +} + +fn main(): i64 { + let g: Color = .green; + let b: Color = .blue; + return (g as i64) + (b as i64); +} diff --git a/test/toy/cases/38_declarations_alias_extern.expected b/test/toy/cases/38_declarations_alias_extern.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/38_declarations_alias_extern.toy b/test/toy/cases/38_declarations_alias_extern.toy @@ -0,0 +1,15 @@ +type Word = i64; + +extern fn imported(x: Word): Word; +extern var errno: i64; + +fn add(a: Word, b: Word): Word { + return a + b; +} + +pub alias exported_add = add; + +fn main(): i64 { + let x: Word = 40; + return add(x, 2); +} diff --git a/test/toy/cases/39_pointer_to_array_index.expected b/test/toy/cases/39_pointer_to_array_index.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/39_pointer_to_array_index.toy b/test/toy/cases/39_pointer_to_array_index.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var xs: [2]i64 = [40, 2]; + let p: *[2]i64 = &xs; + return p[0] + p[1]; +} diff --git a/test/toy/cases/40_alloca_memmove.expected b/test/toy/cases/40_alloca_memmove.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/40_alloca_memmove.toy b/test/toy/cases/40_alloca_memmove.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + let src: *i64 = @alloca<i64>(1, 8); + let dst: *i64 = @alloca<i64>(1, 8); + src[0] = 42; + @memmove(dst, src, 8, 8); + return dst[0]; +} diff --git a/test/toy/cases/41_if_expression.expected b/test/toy/cases/41_if_expression.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/41_if_expression.toy b/test/toy/cases/41_if_expression.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + let x: i64 = if 1 { + 40 + } else { + 2 + }; + return x + 2; +} diff --git a/test/toy/cases/42_switch_expression.expected b/test/toy/cases/42_switch_expression.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/42_switch_expression.toy b/test/toy/cases/42_switch_expression.toy @@ -0,0 +1,15 @@ +fn main(): i64 { + let tag: i64 = 2; + let value: i64 = switch tag { + 0 { + 10 + } + 1, 2 { + 40 + } + default { + 0 + } + }; + return value + 2; +} diff --git a/test/toy/cases/43_while_expression_break.expected b/test/toy/cases/43_while_expression_break.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/43_while_expression_break.toy b/test/toy/cases/43_while_expression_break.toy @@ -0,0 +1,13 @@ +fn main(): i64 { + var i: i64 = 0; + let found: i64 = while<i64> i < 5 { + if i == 3 { + break i; + } + i = i + 1; + continue; + } else { + 99 + }; + return found + 39; +} diff --git a/test/toy/cases/44_attribute_syntax.expected b/test/toy/cases/44_attribute_syntax.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/44_attribute_syntax.toy b/test/toy/cases/44_attribute_syntax.toy @@ -0,0 +1,5 @@ +pub fn @[.hot, .section(".text.hot")] +main(): i64 { + var @[.static] id: i64 = 40; + return id + 2; +} diff --git a/test/toy/cases/45_qual_addrspace_types.expected b/test/toy/cases/45_qual_addrspace_types.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/45_qual_addrspace_types.toy b/test/toy/cases/45_qual_addrspace_types.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 42; + let p: *addrspace(0) const i64 = &x; + return p.*; +} diff --git a/test/toy/cases/46_static_local.expected b/test/toy/cases/46_static_local.expected @@ -0,0 +1 @@ +83 diff --git a/test/toy/cases/46_static_local.toy b/test/toy/cases/46_static_local.toy @@ -0,0 +1,9 @@ +fn next_id(): i64 { + var @[.static] id: i64 = 40; + id = id + 1; + return id; +} + +fn main(): i64 { + return next_id() + next_id(); +} diff --git a/test/toy/cases/47_target_arch_switch.expected b/test/toy/cases/47_target_arch_switch.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/47_target_arch_switch.toy b/test/toy/cases/47_target_arch_switch.toy @@ -0,0 +1,17 @@ +fn main(): i64 { + let value: i64 = switch @target_arch() { + .arm64 { + 40 + } + .x64 { + 40 + } + .rv64 { + 40 + } + default { + 0 + } + }; + return value + 2; +} diff --git a/test/toy/cases/48_supports_callconv.expected b/test/toy/cases/48_supports_callconv.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/48_supports_callconv.toy b/test/toy/cases/48_supports_callconv.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + let value: i64 = if @supports_callconv(.target_c) { + 40 + } else { + 0 + }; + return value + 2; +} diff --git a/test/toy/cases/49_typed_asm_void.expected b/test/toy/cases/49_typed_asm_void.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/49_typed_asm_void.toy b/test/toy/cases/49_typed_asm_void.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), inputs()); + return 42; +} diff --git a/test/toy/cases/50_switch_statement.expected b/test/toy/cases/50_switch_statement.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/50_switch_statement.toy b/test/toy/cases/50_switch_statement.toy @@ -0,0 +1,15 @@ +fn main(): i64 { + var value: i64 = 0; + switch 2 { + 1 { + value = 1; + } + 2 { + value = 42; + } + default { + value = 7; + } + } + return value; +} diff --git a/test/toy/cases/51_labeladdr_goto.expected b/test/toy/cases/51_labeladdr_goto.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/51_labeladdr_goto.toy b/test/toy/cases/51_labeladdr_goto.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + label done; + let target: *void = @labeladdr(done); + goto *target within (done); + return 0; +done: + return 42; +} diff --git a/test/toy/cases/52_global_array_data.expected b/test/toy/cases/52_global_array_data.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/52_global_array_data.toy b/test/toy/cases/52_global_array_data.toy @@ -0,0 +1,5 @@ +pub let table: [2]i64 = [40, 2]; + +fn main(): i64 { + return table[0] + table[1]; +} diff --git a/test/toy/cases/53_conversion_builtins.expected b/test/toy/cases/53_conversion_builtins.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/53_conversion_builtins.toy b/test/toy/cases/53_conversion_builtins.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + let p: *i64 = @int_to_ptr<*i64>(0); + if p == NULL as *i64 { + return @zext<i64>(42 as i32); + } + return 0; +} diff --git a/test/toy/cases/54_scalar_intrinsics.expected b/test/toy/cases/54_scalar_intrinsics.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/54_scalar_intrinsics.toy b/test/toy/cases/54_scalar_intrinsics.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + var x: i64 = 1; + let p: *i64 = @assume_aligned<*i64>(&x, 8); + @prefetch(p); + return @fma(6.0, 7.0, 0.0) as i64; +} diff --git a/test/toy/cases/55_block_scope_shadow.expected b/test/toy/cases/55_block_scope_shadow.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/55_block_scope_shadow.toy b/test/toy/cases/55_block_scope_shadow.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + let x: i64 = 40; + { + let x: i64 = 1; + } + return x + 2; +} diff --git a/test/toy/cases/56_i128_scalar.expected b/test/toy/cases/56_i128_scalar.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/56_i128_scalar.toy b/test/toy/cases/56_i128_scalar.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @sizeof<i128>() + 26; +} diff --git a/test/toy/cases/57_string_escapes.expected b/test/toy/cases/57_string_escapes.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/57_string_escapes.toy b/test/toy/cases/57_string_escapes.toy @@ -0,0 +1,5 @@ +pub let data: [4]u8 = "\x28\x0a\t\0"; + +fn main(): i64 { + return (data[0] as i64) + 2; +} diff --git a/test/toy/cases/58_overflow_record.expected b/test/toy/cases/58_overflow_record.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/58_overflow_record.toy b/test/toy/cases/58_overflow_record.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @add_overflow<i64>(40, 2).value; +} diff --git a/test/toy/cases/59_atomic_cmpxchg_record.expected b/test/toy/cases/59_atomic_cmpxchg_record.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/59_atomic_cmpxchg_record.toy b/test/toy/cases/59_atomic_cmpxchg_record.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 40; + let ok: bool = @atomic_cmpxchg<i64>(&x, 40, 42, .seq_cst, .relaxed, .strong).ok; + return x - ((ok as i64) - 1); +} diff --git a/test/toy/cases/60_atomic_queries.expected b/test/toy/cases/60_atomic_queries.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/60_atomic_queries.toy b/test/toy/cases/60_atomic_queries.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + if @atomic_is_legal<i64>(.seq_cst) and @atomic_is_lock_free<i64>() { + return 42; + } + return 0; +} diff --git a/test/toy/cases/61_labeled_loop.expected b/test/toy/cases/61_labeled_loop.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/61_labeled_loop.toy b/test/toy/cases/61_labeled_loop.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + var i: i64 = 0; +outer: while i < 10 { + i = i + 1; + while 1 { + break outer; + } + } + return i + 41; +} diff --git a/test/toy/cases/62_decl_data_attrs.expected b/test/toy/cases/62_decl_data_attrs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/62_decl_data_attrs.objdump b/test/toy/cases/62_decl_data_attrs.objdump @@ -0,0 +1,5 @@ +decorated_alias +w F __TEXT,__text 0000000000000000 _decorated +2**4 +g C *UND* 0000000000000008 _tentative +w F __TEXT,__text 0000000000000000 _decorated_alias diff --git a/test/toy/cases/62_decl_data_attrs.toy b/test/toy/cases/62_decl_data_attrs.toy @@ -0,0 +1,17 @@ +pub fn @[.bind(.weak), .visibility(.hidden), .section(".text.hot"), .hot, + .stack_align(16), .target_features("")] +decorated(x: i64): i64 { + return x + 1; +} + +pub let @[.section(".rodata.tests"), .align(16), .used, .readonly] +table: [2]i64 = [20, 21]; + +pub var @[.common, .align(8)] tentative: i64; + +pub alias @[.bind(.weak), .visibility(.hidden)] decorated_alias = decorated; + +fn main(): i64 { + tentative = table[0] + table[1]; + return decorated(tentative); +} diff --git a/test/toy/cases/63_memory_flags.expected b/test/toy/cases/63_memory_flags.expected @@ -0,0 +1 @@ +8 diff --git a/test/toy/cases/63_memory_flags.toy b/test/toy/cases/63_memory_flags.toy @@ -0,0 +1,11 @@ +fn main(): i64 { + let src: *i64 = @alloca<i64>(4, 8); + let dst: *i64 = @alloca<i64>(4, 8); + src[0] = 1; + src[1] = 2; + src[2] = 3; + src[3] = 4; + @memcpy(dst, src, 32, 8, .volatile, .nontemporal); + @memset(&dst[1], 0, 8, 8, .invariant); + return dst[0] + dst[1] + dst[2] + dst[3]; +} diff --git a/test/toy/cases/64_target_feature_queries.expected b/test/toy/cases/64_target_feature_queries.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/64_target_feature_queries.toy b/test/toy/cases/64_target_feature_queries.toy @@ -0,0 +1,11 @@ +fn main(): i64 { + let weak_ok: bool = @supports_symbol_feature(.weak); + let common_ok: bool = @supports_symbol_feature(.common); + let strict: bool = @has_backend_feature(.strict_alignment); + let red_zone: bool = @has_backend_feature(.red_zone); + if (weak_ok or !weak_ok) and (common_ok or !common_ok) and + (strict or !strict) and (red_zone or !red_zone) { + return 42; + } + return 1; +} diff --git a/test/toy/cases/65_rounding_conversions.expected b/test/toy/cases/65_rounding_conversions.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/65_rounding_conversions.toy b/test/toy/cases/65_rounding_conversions.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + let a: f64 = @sint_to_float<f64>(40, .default); + let b: f64 = @uint_to_float<f64>(2, .nearest_even); + let c: i64 = @float_to_sint<i64>(a + b + 0.75, .toward_zero); + let d: u64 = @float_to_uint<u64>(0.9, .down); + return c + d; +} diff --git a/test/toy/cases/66_anonymous_record_type.expected b/test/toy/cases/66_anonymous_record_type.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/66_anonymous_record_type.toy b/test/toy/cases/66_anonymous_record_type.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let r: record { a: i64, b: i64 } = { a: 40, b: 2 }; + return r.a + r.b; +} diff --git a/test/toy/cases/67_abi_attrs.expected b/test/toy/cases/67_abi_attrs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/67_abi_attrs.toy b/test/toy/cases/67_abi_attrs.toy @@ -0,0 +1,9 @@ +fn load_first(p @[.nonnull, .readonly, .align(8), .dereferenceable(8)]: *i64): i64 @[.zeroext] { + return p[0]; +} + +fn main(): i64 { + let p: *i64 = @alloca<i64>(1, 8); + p[0] = 42; + return load_first(p); +} diff --git a/test/toy/cases/68_field_attrs.expected b/test/toy/cases/68_field_attrs.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/68_field_attrs.toy b/test/toy/cases/68_field_attrs.toy @@ -0,0 +1,11 @@ +record Padded { + x @[.align(16)]: i64, + y: i64, +} + +fn main(): i64 { + if @alignof<Padded>() >= 16 { + return 42; + } + return 1; +} diff --git a/test/toy/cases/69_if_value_block_statements.expected b/test/toy/cases/69_if_value_block_statements.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/69_if_value_block_statements.toy b/test/toy/cases/69_if_value_block_statements.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + let x: i64 = if 1 { + let base = 40; + base + 2 + } else { + let base = 1; + base + }; + return x; +} diff --git a/test/toy/cases/70_labeled_switch_break.expected b/test/toy/cases/70_labeled_switch_break.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/70_labeled_switch_break.toy b/test/toy/cases/70_labeled_switch_break.toy @@ -0,0 +1,14 @@ +fn main(): i64 { + var x: i64 = 1; +outer: switch 0 { + 0 { + x = 42; + break outer; + x = 1; + } + default { + x = 2; + } + } + return x; +} diff --git a/test/toy/cases/71_forward_records.expected b/test/toy/cases/71_forward_records.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/71_forward_records.toy b/test/toy/cases/71_forward_records.toy @@ -0,0 +1,18 @@ +record A; +record B; + +record A { + b: *B, + value: i64, +} + +record B { + a: *A, + value: i64, +} + +fn main(): i64 { + let a: A = A { value: 40 }; + let b: B = B { a: &a, value: 2 }; + return a.value + b.value; +} diff --git a/test/toy/cases/72_global_record_data.expected b/test/toy/cases/72_global_record_data.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/72_global_record_data.toy b/test/toy/cases/72_global_record_data.toy @@ -0,0 +1,10 @@ +record GPair { + a: i64, + b: i64, +} + +pub let gp: GPair = GPair { a: 40, b: 2 }; + +fn main(): i64 { + return gp.a + gp.b; +} diff --git a/test/toy/cases/73_atomic_access_group.expected b/test/toy/cases/73_atomic_access_group.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/73_atomic_access_group.toy b/test/toy/cases/73_atomic_access_group.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @atomic_store<i64>(&x, 42, .seq_cst, access(.align(8), .volatile)); + return @atomic_load<i64>(&x, .seq_cst, access(.align(8), .volatile)); +} diff --git a/test/toy/cases/74_atomic_rmw_access.expected b/test/toy/cases/74_atomic_rmw_access.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/74_atomic_rmw_access.toy b/test/toy/cases/74_atomic_rmw_access.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 40; + let old: i64 = @atomic_rmw<i64>(.add, &x, 2, .seq_cst, access(.align(8))); + return x + old - 40; +} diff --git a/test/toy/cases/75_atomic_cmpxchg_access.expected b/test/toy/cases/75_atomic_cmpxchg_access.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/75_atomic_cmpxchg_access.toy b/test/toy/cases/75_atomic_cmpxchg_access.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + var x: i64 = 1; + let r = @atomic_cmpxchg<i64>(&x, 1, 42, .seq_cst, .seq_cst, .strong, access(.align(8))); + if r.ok { + return x; + } + return 1; +} diff --git a/test/toy/cases/76_atomic_query_access.expected b/test/toy/cases/76_atomic_query_access.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/76_atomic_query_access.toy b/test/toy/cases/76_atomic_query_access.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + let legal: bool = @atomic_is_legal<i64>(.seq_cst, access(.align(8))); + let lock_free: bool = @atomic_is_lock_free<i64>(access(.align(8))); + if (legal or !legal) and (lock_free or !lock_free) { + return 42; + } + return 1; +} diff --git a/test/toy/cases/77_atomic_keyword_ops.expected b/test/toy/cases/77_atomic_keyword_ops.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/77_atomic_keyword_ops.toy b/test/toy/cases/77_atomic_keyword_ops.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 47; + @atomic_rmw<i64>(.and, &x, 42, .seq_cst); + return x; +} diff --git a/test/toy/cases/78_null_expected_pointer.expected b/test/toy/cases/78_null_expected_pointer.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/78_null_expected_pointer.toy b/test/toy/cases/78_null_expected_pointer.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + let p: *i64 = NULL; + if p == NULL as *i64 { + return 42; + } + return 1; +} diff --git a/test/toy/cases/79_enum_argument_context.expected b/test/toy/cases/79_enum_argument_context.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/79_enum_argument_context.toy b/test/toy/cases/79_enum_argument_context.toy @@ -0,0 +1,11 @@ +enum Answer: i64 { + .ok = 42, +} + +fn value(x: Answer): i64 { + return x as i64; +} + +fn main(): i64 { + return value(.ok); +} diff --git a/test/toy/cases/80_enum_switch_labels.expected b/test/toy/cases/80_enum_switch_labels.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/80_enum_switch_labels.toy b/test/toy/cases/80_enum_switch_labels.toy @@ -0,0 +1,16 @@ +enum Choice: i64 { + .answer = 7, + .other = 8, +} + +fn main(): i64 { + let c: Choice = .answer; + switch c { + .answer { + return 42; + } + default { + return 1; + } + } +} diff --git a/test/toy/cases/81_global_float_data.expected b/test/toy/cases/81_global_float_data.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/81_global_float_data.toy b/test/toy/cases/81_global_float_data.toy @@ -0,0 +1,5 @@ +pub let f: f64 = 42.0; + +fn main(): i64 { + return f as i64; +} diff --git a/test/toy/cases/82_unreachable_builtin.expected b/test/toy/cases/82_unreachable_builtin.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/82_unreachable_builtin.toy b/test/toy/cases/82_unreachable_builtin.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + if 1 { + return 42; + } + @unreachable(); + return 1; +} diff --git a/test/toy/cases/83_pointer_field_access.expected b/test/toy/cases/83_pointer_field_access.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/83_pointer_field_access.toy b/test/toy/cases/83_pointer_field_access.toy @@ -0,0 +1,9 @@ +record Box { + value: i64, +} + +fn main(): i64 { + let b: Box = Box { value: 42 }; + let p: *Box = &b; + return p.value; +} diff --git a/test/toy/cases/84_field_assignment.expected b/test/toy/cases/84_field_assignment.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/84_field_assignment.toy b/test/toy/cases/84_field_assignment.toy @@ -0,0 +1,9 @@ +record Cell { + value: i64, +} + +fn main(): i64 { + var c: Cell = Cell { value: 1 }; + c.value = 42; + return c.value; +} diff --git a/test/toy/cases/85_packed_record_attr.expected b/test/toy/cases/85_packed_record_attr.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/85_packed_record_attr.toy b/test/toy/cases/85_packed_record_attr.toy @@ -0,0 +1,8 @@ +record @[.packed] Packed { + a: i8, + b: i64, +} + +fn main(): i64 { + return @offsetof<Packed>(b) + 41; +} diff --git a/test/toy/cases/86_address_of_field.expected b/test/toy/cases/86_address_of_field.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/86_address_of_field.toy b/test/toy/cases/86_address_of_field.toy @@ -0,0 +1,9 @@ +record Cell2 { + value: i64, +} + +fn main(): i64 { + var c: Cell2 = Cell2 { value: 42 }; + let p: *i64 = &c.value; + return p[0]; +} diff --git a/test/toy/cases/87_expect_preserves_type.expected b/test/toy/cases/87_expect_preserves_type.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/87_expect_preserves_type.toy b/test/toy/cases/87_expect_preserves_type.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let x: i32 = @expect(42 as i32, 42 as i32); + return x as i64; +} diff --git a/test/toy/cases/88_scalar_intrinsic_preserves_type.expected b/test/toy/cases/88_scalar_intrinsic_preserves_type.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/88_scalar_intrinsic_preserves_type.toy b/test/toy/cases/88_scalar_intrinsic_preserves_type.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + let x: i32 = @popcount(7 as i32); + let y: i32 = @bswap(0 as i32); + return (x + y) as i64 + 39; +} diff --git a/test/toy/cases/89_labeled_while_expression.expected b/test/toy/cases/89_labeled_while_expression.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/89_labeled_while_expression.toy b/test/toy/cases/89_labeled_while_expression.toy @@ -0,0 +1,13 @@ +fn main(): i64 { + var i: i64 = 0; + let found: i64 = outer: while<i64> i < 10 { + i = i + 1; + if i == 3 { + break outer 42; + } + continue outer; + } else { + 1 + }; + return found; +} diff --git a/test/toy/cases/90_continue_through_switch.expected b/test/toy/cases/90_continue_through_switch.expected @@ -0,0 +1 @@ +12 diff --git a/test/toy/cases/90_continue_through_switch.toy b/test/toy/cases/90_continue_through_switch.toy @@ -0,0 +1,16 @@ +fn main(): i64 { + var i: i64 = 0; + var sum: i64 = 0; + while i < 5 { + i = i + 1; + switch i { + 3 { + continue; + } + default { + } + } + sum = sum + i; + } + return sum; +} diff --git a/test/toy/cases/91_exhaustive_enum_switch_expression.expected b/test/toy/cases/91_exhaustive_enum_switch_expression.expected @@ -0,0 +1 @@ +20 diff --git a/test/toy/cases/91_exhaustive_enum_switch_expression.toy b/test/toy/cases/91_exhaustive_enum_switch_expression.toy @@ -0,0 +1,21 @@ +enum Color: i64 { + .red = 1, + .green = 2, + .blue = 3, +} + +fn main(): i64 { + let c: Color = .green; + let value: i64 = switch c { + .red { + 10 + } + .green { + 20 + } + .blue { + 30 + } + }; + return value; +} diff --git a/test/toy/cases/92_parenthesized_precedence_islands.expected b/test/toy/cases/92_parenthesized_precedence_islands.expected @@ -0,0 +1 @@ +24 diff --git a/test/toy/cases/92_parenthesized_precedence_islands.toy b/test/toy/cases/92_parenthesized_precedence_islands.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return (1 + 2) << 3; +} diff --git a/test/toy/cases/93_data_pad_align.expected b/test/toy/cases/93_data_pad_align.expected @@ -0,0 +1 @@ +24 diff --git a/test/toy/cases/93_data_pad_align.toy b/test/toy/cases/93_data_pad_align.toy @@ -0,0 +1,6 @@ +pub let data: [6]u8 = [1, @pad(2, 7), 4, @align(2), 5]; + +fn main(): i64 { + return (data[0] as i64) + (data[1] as i64) + (data[2] as i64) + + (data[3] as i64) + (data[4] as i64) + (data[5] as i64); +} diff --git a/test/toy/cases/94_restrict_pointer_type.expected b/test/toy/cases/94_restrict_pointer_type.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/94_restrict_pointer_type.toy b/test/toy/cases/94_restrict_pointer_type.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 41; + let p: restrict *i64 = &x; + return p.* + 1; +} diff --git a/test/toy/cases/95_static_local_string_data.expected b/test/toy/cases/95_static_local_string_data.expected @@ -0,0 +1 @@ +77 diff --git a/test/toy/cases/95_static_local_string_data.toy b/test/toy/cases/95_static_local_string_data.toy @@ -0,0 +1,9 @@ +fn read_data(): i64 { + let @[.static] data: [4]u8 = "A\x02\n\0"; + return (data[0] as i64) + (data[1] as i64) + (data[2] as i64) + + (data[3] as i64); +} + +fn main(): i64 { + return read_data(); +} diff --git a/test/toy/cases/96_data_relocations.expected b/test/toy/cases/96_data_relocations.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/96_data_relocations.toy b/test/toy/cases/96_data_relocations.toy @@ -0,0 +1,8 @@ +pub let before: i32 = 1; +pub let after: i32 = 2; + +pub let rels: [2]i32 = [@pcrel(after, 0), 7]; + +fn main(): i64 { + return 42; +} diff --git a/test/toy/cases/97_let_pointer_pointee_assignment.expected b/test/toy/cases/97_let_pointer_pointee_assignment.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/97_let_pointer_pointee_assignment.toy b/test/toy/cases/97_let_pointer_pointee_assignment.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + var x: i64 = 1; + let p: *i64 = &x; + p.* = 42; + return x; +} diff --git a/test/toy/cases/98_tuple_offsetof.expected b/test/toy/cases/98_tuple_offsetof.expected @@ -0,0 +1 @@ +8 diff --git a/test/toy/cases/98_tuple_offsetof.toy b/test/toy/cases/98_tuple_offsetof.toy @@ -0,0 +1,8 @@ +tuple Pair64 { + i64, + i64, +} + +fn main(): i64 { + return @offsetof<Pair64>(1); +} diff --git a/test/toy/cases/99_global_tuple_data.expected b/test/toy/cases/99_global_tuple_data.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/99_global_tuple_data.toy b/test/toy/cases/99_global_tuple_data.toy @@ -0,0 +1,10 @@ +tuple GPair { + i64, + i64, +} + +pub let gp: GPair = GPair { 40, 2 }; + +fn main(): i64 { + return gp.0 + gp.1; +} diff --git a/test/toy/demo.toy b/test/toy/demo.toy @@ -1,25 +1,25 @@ -let seed: int = 5; -var counter: int = 3; -var counter_ptr: *int = &counter; +let seed: i64 = 5; +var counter: i64 = 3; +var counter_ptr: *i64 = &counter; -fn id(x: int): int { +fn id(x: i64): i64 { return x; } -fn id_tail(x: int): int { +fn id_tail(x: i64): i64 { return tail id(x); } -fn fib(n: int): int { +fn fib(n: i64): i64 { if n < 2 { return n; } return fib(n - 1) + fib(n - 2); } -fn sum_to(n: int): int { - let i: int = 0; - let sum: int = 0; +fn sum_to(n: i64): i64 { + var i: i64 = 0; + var sum: i64 = 0; while i < n { i = i + 1; if i == 3 { @@ -33,37 +33,39 @@ fn sum_to(n: int): int { return sum; } -fn memory_demo(): int { - let a: int = 0; - let b: int = 0; - let p: *int = &a; - let q: *int = &b; +fn memory_demo(): i64 { + var a: i64 = 0; + var b: i64 = 0; + let p: *i64 = &a; + let q: *i64 = &b; - *index(p, 0) = 42; - memset(q, 0, 8); - memcpy(q, p, 8); - return *index(q, 0); + p[0] = 42; + @memset(q, 0, 8, 8); + @memcpy(q, p, 8, 8); + return q[0]; } -fn atomic_demo(): int { - atomic_store(counter_ptr, 10); - let before: int = atomic_add(counter_ptr, 5); - let after: int = atomic_load(counter_ptr); - let ok: int = atomic_cas_ok(counter_ptr, after, 21); - fence(); - return before + after + ok; +fn atomic_demo(): i64 { + @atomic_store<i64>(counter_ptr, 10, .seq_cst); + let before: i64 = @atomic_rmw<i64>(.add, counter_ptr, 5, .seq_cst); + let after: i64 = @atomic_load<i64>(counter_ptr, .seq_cst); + let cas = @atomic_cmpxchg<i64>(counter_ptr, after, 21, .seq_cst, .relaxed, + .strong); + @atomic_fence(.seq_cst); + return before + after + (cas.ok as i64); } -fn intrin_demo(x: int): int { - return popcount(x) + ctz(8) + clz(1) + expect(1, 1) + bswap(1); +fn intrin_demo(x: i64): i64 { + return @popcount(x) + @ctz(8) + @clz(1) + @expect(1, 1) + @bswap(1); } -fn api_demo(): int { - return typecheck() + byteconst() + fieldtest() + asm(arch("nop", "", "")); +fn api_demo(): i64 { + @asm<void>("", outputs(), inputs()); + return 1 + 42 + 42; } -fn main(): int { - let local: int = seed + *counter_ptr; +fn main(): i64 { + let local: i64 = seed + counter_ptr.*; counter = counter + 2; return id_tail(local) + fib(6) diff --git a/test/toy/err/common_readonly.expected b/test/toy/err/common_readonly.expected @@ -0,0 +1 @@ +common object must be mutable diff --git a/test/toy/err/common_readonly.toy b/test/toy/err/common_readonly.toy @@ -0,0 +1 @@ +let @[.common] tentative: i64; diff --git a/test/toy/err/common_with_initializer.expected b/test/toy/err/common_with_initializer.expected @@ -0,0 +1 @@ +common object cannot have initializer diff --git a/test/toy/err/common_with_initializer.toy b/test/toy/err/common_with_initializer.toy @@ -0,0 +1 @@ +var @[.common] tentative: i64 = 1; diff --git a/test/toy/err/compile_error_builtin.expected b/test/toy/err/compile_error_builtin.expected @@ -0,0 +1 @@ +stop here diff --git a/test/toy/err/compile_error_builtin.toy b/test/toy/err/compile_error_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @compile_error("stop here"); +} diff --git a/test/toy/err/continue_to_switch.expected b/test/toy/err/continue_to_switch.expected @@ -0,0 +1 @@ +continue target is not a loop diff --git a/test/toy/err/continue_to_switch.toy b/test/toy/err/continue_to_switch.toy @@ -0,0 +1,10 @@ +fn main(): i64 { +outer: switch 0 { + 0 { + continue outer; + } + default { + } + } + return 0; +} diff --git a/test/toy/err/data_pcrel_bad_width.expected b/test/toy/err/data_pcrel_bad_width.expected @@ -0,0 +1 @@ +pcrel requires i32/i64 initializer slot diff --git a/test/toy/err/data_pcrel_bad_width.toy b/test/toy/err/data_pcrel_bad_width.toy @@ -0,0 +1,6 @@ +pub let target_value: i32 = 1; +pub let bad: [1]i16 = [@pcrel(target_value, 0)]; + +fn main(): i64 { + return 0; +} diff --git a/test/toy/err/data_pcrel_outside_initializer.expected b/test/toy/err/data_pcrel_outside_initializer.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/data_pcrel_outside_initializer.toy b/test/toy/err/data_pcrel_outside_initializer.toy @@ -0,0 +1,5 @@ +pub let target_value: i32 = 1; + +fn main(): i64 { + return @pcrel(target_value, 0) as i64; +} diff --git a/test/toy/err/direct_recursive_record.expected b/test/toy/err/direct_recursive_record.expected @@ -0,0 +1 @@ +incomplete record type requires pointer diff --git a/test/toy/err/direct_recursive_record.toy b/test/toy/err/direct_recursive_record.toy @@ -0,0 +1,3 @@ +record Bad { + next: Bad, +} diff --git a/test/toy/err/forward_record_pointer_arg_mismatch.expected b/test/toy/err/forward_record_pointer_arg_mismatch.expected @@ -0,0 +1 @@ +function argument type mismatch diff --git a/test/toy/err/forward_record_pointer_arg_mismatch.toy b/test/toy/err/forward_record_pointer_arg_mismatch.toy @@ -0,0 +1,11 @@ +record A; +record B; + +fn take_a(a: *A): i64 { + return 1; +} + +fn main(): i64 { + let b: *B = NULL as *B; + return take_a(b); +} diff --git a/test/toy/err/forward_record_pointer_array_literal_mismatch.expected b/test/toy/err/forward_record_pointer_array_literal_mismatch.expected @@ -0,0 +1 @@ +array element type mismatch diff --git a/test/toy/err/forward_record_pointer_array_literal_mismatch.toy b/test/toy/err/forward_record_pointer_array_literal_mismatch.toy @@ -0,0 +1,8 @@ +record A; +record B; + +fn main(): i64 { + let b: *B = NULL as *B; + let xs: [1]*A = [b]; + return 0; +} diff --git a/test/toy/err/forward_record_pointer_assign_mismatch.expected b/test/toy/err/forward_record_pointer_assign_mismatch.expected @@ -0,0 +1 @@ +type mismatch in assignment diff --git a/test/toy/err/forward_record_pointer_assign_mismatch.toy b/test/toy/err/forward_record_pointer_assign_mismatch.toy @@ -0,0 +1,9 @@ +record A; +record B; + +fn main(): i64 { + var a: *A = NULL as *A; + let b: *B = NULL as *B; + a = b; + return 0; +} diff --git a/test/toy/err/forward_record_pointer_break_mismatch.expected b/test/toy/err/forward_record_pointer_break_mismatch.expected @@ -0,0 +1 @@ +break value type mismatch diff --git a/test/toy/err/forward_record_pointer_break_mismatch.toy b/test/toy/err/forward_record_pointer_break_mismatch.toy @@ -0,0 +1,12 @@ +record A; +record B; + +fn main(): i64 { + let b: *B = NULL as *B; + let a: *A = while<*A> 1 { + break b; + } else { + NULL as *A + }; + return 0; +} diff --git a/test/toy/err/forward_record_pointer_fnptr_arg_mismatch.expected b/test/toy/err/forward_record_pointer_fnptr_arg_mismatch.expected @@ -0,0 +1 @@ +function argument type mismatch diff --git a/test/toy/err/forward_record_pointer_fnptr_arg_mismatch.toy b/test/toy/err/forward_record_pointer_fnptr_arg_mismatch.toy @@ -0,0 +1,12 @@ +record A; +record B; + +fn takes_a(p: *A): i64 { + return 0; +} + +fn main(): i64 { + let fp: *fn(*A): i64 = &takes_a; + let b: *B = NULL as *B; + return fp(b); +} diff --git a/test/toy/err/forward_record_pointer_let_mismatch.expected b/test/toy/err/forward_record_pointer_let_mismatch.expected @@ -0,0 +1 @@ +type mismatch in let initializer diff --git a/test/toy/err/forward_record_pointer_let_mismatch.toy b/test/toy/err/forward_record_pointer_let_mismatch.toy @@ -0,0 +1,8 @@ +record A; +record B; + +fn main(): i64 { + let b: *B = NULL as *B; + let a: *A = b; + return 0; +} diff --git a/test/toy/err/forward_record_pointer_record_literal_mismatch.expected b/test/toy/err/forward_record_pointer_record_literal_mismatch.expected @@ -0,0 +1 @@ +record field type mismatch diff --git a/test/toy/err/forward_record_pointer_record_literal_mismatch.toy b/test/toy/err/forward_record_pointer_record_literal_mismatch.toy @@ -0,0 +1,12 @@ +record A; +record B; + +record Holder { + ptr: *A, +} + +fn main(): i64 { + let b: *B = NULL as *B; + let h: Holder = Holder { ptr: b }; + return 0; +} diff --git a/test/toy/err/forward_record_pointer_return_mismatch.expected b/test/toy/err/forward_record_pointer_return_mismatch.expected @@ -0,0 +1 @@ +return type mismatch diff --git a/test/toy/err/forward_record_pointer_return_mismatch.toy b/test/toy/err/forward_record_pointer_return_mismatch.toy @@ -0,0 +1,11 @@ +record A; +record B; + +fn f(): *A { + let b: *B = NULL as *B; + return b; +} + +fn main(): i64 { + return f() == NULL as *A; +} diff --git a/test/toy/err/forward_record_pointer_tail_mismatch.expected b/test/toy/err/forward_record_pointer_tail_mismatch.expected @@ -0,0 +1 @@ +tail call signature mismatch diff --git a/test/toy/err/forward_record_pointer_tail_mismatch.toy b/test/toy/err/forward_record_pointer_tail_mismatch.toy @@ -0,0 +1,14 @@ +record A; +record B; + +fn g(): *B { + return NULL as *B; +} + +fn f(): *A { + return tail g(); +} + +fn main(): i64 { + return f() == NULL as *A; +} diff --git a/test/toy/err/immutable_local_assign.expected b/test/toy/err/immutable_local_assign.expected @@ -0,0 +1 @@ +cannot assign to immutable local diff --git a/test/toy/err/immutable_local_assign.toy b/test/toy/err/immutable_local_assign.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + let x: i64 = 1; + x = 2; + return x; +} diff --git a/test/toy/err/immutable_local_field_assign.expected b/test/toy/err/immutable_local_field_assign.expected @@ -0,0 +1 @@ +cannot assign to immutable local diff --git a/test/toy/err/immutable_local_field_assign.toy b/test/toy/err/immutable_local_field_assign.toy @@ -0,0 +1,9 @@ +record Box { + value: i64, +} + +fn main(): i64 { + let b: Box = Box { value: 1 }; + b.value = 2; + return b.value; +} diff --git a/test/toy/err/invalid_address_of_literal.expected b/test/toy/err/invalid_address_of_literal.expected @@ -0,0 +1 @@ +expected identifier after '&' diff --git a/test/toy/err/invalid_address_of_literal.toy b/test/toy/err/invalid_address_of_literal.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let p: *i64 = &1; + return 0; +} diff --git a/test/toy/err/invalid_addrspace_negative.expected b/test/toy/err/invalid_addrspace_negative.expected @@ -0,0 +1 @@ +invalid address space diff --git a/test/toy/err/invalid_addrspace_negative.toy b/test/toy/err/invalid_addrspace_negative.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let p: *addrspace(-1) i64 = NULL as *addrspace(0) i64; + return 0; +} diff --git a/test/toy/err/invalid_alias_object_attribute.expected b/test/toy/err/invalid_alias_object_attribute.expected @@ -0,0 +1 @@ +invalid alias attribute diff --git a/test/toy/err/invalid_alias_object_attribute.toy b/test/toy/err/invalid_alias_object_attribute.toy @@ -0,0 +1,5 @@ +fn f(): i64 { + return 0; +} + +alias @[.section(".aliases")] f_alias = f; diff --git a/test/toy/err/invalid_array_literal_too_many.expected b/test/toy/err/invalid_array_literal_too_many.expected @@ -0,0 +1 @@ +too many array elements diff --git a/test/toy/err/invalid_array_literal_too_many.toy b/test/toy/err/invalid_array_literal_too_many.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let xs: [1]i64 = [1, 2]; + return xs[0]; +} diff --git a/test/toy/err/invalid_asm_duplicate_clobber_abi.expected b/test/toy/err/invalid_asm_duplicate_clobber_abi.expected @@ -0,0 +1 @@ +duplicate asm clobber_abi group diff --git a/test/toy/err/invalid_asm_duplicate_clobber_abi.toy b/test/toy/err/invalid_asm_duplicate_clobber_abi.toy @@ -0,0 +1,9 @@ +fn main(): i64 { + @asm<void>( + "", + outputs(), + clobber_abi(.caller_saved), + clobber_abi(.caller_saved) + ); + return 0; +} diff --git a/test/toy/err/invalid_asm_duplicate_clobbers.expected b/test/toy/err/invalid_asm_duplicate_clobbers.expected @@ -0,0 +1 @@ +duplicate asm clobbers group diff --git a/test/toy/err/invalid_asm_duplicate_clobbers.toy b/test/toy/err/invalid_asm_duplicate_clobbers.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), clobbers(), clobbers()); + return 0; +} diff --git a/test/toy/err/invalid_asm_duplicate_flags.expected b/test/toy/err/invalid_asm_duplicate_flags.expected @@ -0,0 +1 @@ +duplicate asm flags group diff --git a/test/toy/err/invalid_asm_duplicate_flags.toy b/test/toy/err/invalid_asm_duplicate_flags.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), flags(.volatile), flags(.nomem)); + return 0; +} diff --git a/test/toy/err/invalid_asm_duplicate_inputs.expected b/test/toy/err/invalid_asm_duplicate_inputs.expected @@ -0,0 +1 @@ +duplicate asm inputs group diff --git a/test/toy/err/invalid_asm_duplicate_inputs.toy b/test/toy/err/invalid_asm_duplicate_inputs.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), inputs(), inputs()); + return 0; +} diff --git a/test/toy/err/invalid_asm_input_constraint.expected b/test/toy/err/invalid_asm_input_constraint.expected @@ -0,0 +1 @@ +unsupported asm input constraint diff --git a/test/toy/err/invalid_asm_input_constraint.toy b/test/toy/err/invalid_asm_input_constraint.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), inputs(in("x", 1))); + return 0; +} diff --git a/test/toy/err/invalid_asm_input_wrapper.expected b/test/toy/err/invalid_asm_input_wrapper.expected @@ -0,0 +1 @@ +expected asm input operand diff --git a/test/toy/err/invalid_asm_input_wrapper.toy b/test/toy/err/invalid_asm_input_wrapper.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), inputs(out("=r", value: i64))); + return 0; +} diff --git a/test/toy/err/invalid_asm_missing_outputs.expected b/test/toy/err/invalid_asm_missing_outputs.expected @@ -0,0 +1 @@ +asm outputs group is required diff --git a/test/toy/err/invalid_asm_missing_outputs.toy b/test/toy/err/invalid_asm_missing_outputs.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm<i64>("", inputs(), clobbers(), flags(.volatile)); +} diff --git a/test/toy/err/invalid_asm_output_constraint.expected b/test/toy/err/invalid_asm_output_constraint.expected @@ -0,0 +1 @@ +unsupported asm output constraint diff --git a/test/toy/err/invalid_asm_output_constraint.toy b/test/toy/err/invalid_asm_output_constraint.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm<i64>("", outputs(out("=m", value: i64))); +} diff --git a/test/toy/err/invalid_asm_record_output_count.expected b/test/toy/err/invalid_asm_record_output_count.expected @@ -0,0 +1 @@ +asm record result output count mismatch diff --git a/test/toy/err/invalid_asm_record_output_count.toy b/test/toy/err/invalid_asm_record_output_count.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + let pair = @asm<record { lo: i64, hi: i64 }>( + "", + outputs(out("=r", lo: i64)), + inputs(), + clobbers(), + flags(.volatile) + ); + return pair.lo; +} diff --git a/test/toy/err/invalid_asm_record_output_name.expected b/test/toy/err/invalid_asm_record_output_name.expected @@ -0,0 +1 @@ +asm record result output mismatch diff --git a/test/toy/err/invalid_asm_record_output_name.toy b/test/toy/err/invalid_asm_record_output_name.toy @@ -0,0 +1,10 @@ +fn main(): i64 { + let pair = @asm<record { lo: i64, hi: i64 }>( + "", + outputs(out("=r", mid: i64), out("=r", lo: i64)), + inputs(), + clobbers(), + flags(.volatile) + ); + return pair.lo; +} diff --git a/test/toy/err/invalid_asm_result_type.expected b/test/toy/err/invalid_asm_result_type.expected @@ -0,0 +1 @@ +asm result type must match single output diff --git a/test/toy/err/invalid_asm_result_type.toy b/test/toy/err/invalid_asm_result_type.toy @@ -0,0 +1,9 @@ +fn main(): i64 { + return @asm<i64>( + "", + outputs(out("=r", value: i32)), + inputs(), + clobbers(), + flags(.volatile) + ); +} diff --git a/test/toy/err/invalid_asm_unknown_clobber_abi.expected b/test/toy/err/invalid_asm_unknown_clobber_abi.expected @@ -0,0 +1 @@ +unknown asm clobber ABI diff --git a/test/toy/err/invalid_asm_unknown_clobber_abi.toy b/test/toy/err/invalid_asm_unknown_clobber_abi.toy @@ -0,0 +1,11 @@ +fn main(): i64 { + @asm<void>( + "", + outputs(), + inputs(), + clobbers(), + flags(.volatile), + clobber_abi(.callee_saved) + ); + return 0; +} diff --git a/test/toy/err/invalid_asm_unknown_flag.expected b/test/toy/err/invalid_asm_unknown_flag.expected @@ -0,0 +1 @@ +unknown asm flag diff --git a/test/toy/err/invalid_asm_unknown_flag.toy b/test/toy/err/invalid_asm_unknown_flag.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>("", outputs(), inputs(), clobbers(), flags(.side_effects)); + return 0; +} diff --git a/test/toy/err/invalid_computed_goto_target_list.expected b/test/toy/err/invalid_computed_goto_target_list.expected @@ -0,0 +1 @@ +unknown label in target list diff --git a/test/toy/err/invalid_computed_goto_target_list.toy b/test/toy/err/invalid_computed_goto_target_list.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + label done; + let target: *void = @labeladdr(done); + goto *target within (missing); +done: + return 0; +} diff --git a/test/toy/err/invalid_extern_data_attribute.expected b/test/toy/err/invalid_extern_data_attribute.expected @@ -0,0 +1 @@ +invalid extern data attribute diff --git a/test/toy/err/invalid_extern_data_attribute.toy b/test/toy/err/invalid_extern_data_attribute.toy @@ -0,0 +1 @@ +extern let @[.retain] retained: i64; diff --git a/test/toy/err/invalid_fn_object_attribute.expected b/test/toy/err/invalid_fn_object_attribute.expected @@ -0,0 +1 @@ +invalid function attribute diff --git a/test/toy/err/invalid_fn_object_attribute.toy b/test/toy/err/invalid_fn_object_attribute.toy @@ -0,0 +1,3 @@ +fn @[.readonly] f(): i64 { + return 0; +} diff --git a/test/toy/err/invalid_null_context.expected b/test/toy/err/invalid_null_context.expected @@ -0,0 +1 @@ +type mismatch in let initializer diff --git a/test/toy/err/invalid_null_context.toy b/test/toy/err/invalid_null_context.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let x: i64 = NULL; + return x; +} diff --git a/test/toy/err/invalid_object_function_attribute.expected b/test/toy/err/invalid_object_function_attribute.expected @@ -0,0 +1 @@ +invalid object attribute diff --git a/test/toy/err/invalid_object_function_attribute.toy b/test/toy/err/invalid_object_function_attribute.toy @@ -0,0 +1,5 @@ +let @[.hot] x: i64 = 0; + +fn main(): i64 { + return x; +} diff --git a/test/toy/err/invalid_record_literal_unknown_field.expected b/test/toy/err/invalid_record_literal_unknown_field.expected @@ -0,0 +1 @@ +unknown record field diff --git a/test/toy/err/invalid_record_literal_unknown_field.toy b/test/toy/err/invalid_record_literal_unknown_field.toy @@ -0,0 +1,8 @@ +record Pair { + a: i64, +} + +fn main(): i64 { + let p: Pair = Pair { b: 1 }; + return 0; +} diff --git a/test/toy/err/invalid_switch_strategy_hint.expected b/test/toy/err/invalid_switch_strategy_hint.expected @@ -0,0 +1 @@ +unknown switch strategy diff --git a/test/toy/err/invalid_switch_strategy_hint.toy b/test/toy/err/invalid_switch_strategy_hint.toy @@ -0,0 +1,8 @@ +fn main(): i64 { + switch @[.not_a_strategy] 1 { + default { + return 0; + } + } + return 0; +} diff --git a/test/toy/err/legacy_asm_arch_selector.expected b/test/toy/err/legacy_asm_arch_selector.expected @@ -0,0 +1 @@ +expected asm template diff --git a/test/toy/err/legacy_asm_arch_selector.toy b/test/toy/err/legacy_asm_arch_selector.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @asm<void>(arch("nop", "", ""), outputs(), inputs()); + return 0; +} diff --git a/test/toy/err/legacy_asm_builtin.expected b/test/toy/err/legacy_asm_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_builtin.toy b/test/toy/err/legacy_asm_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm(""); +} diff --git a/test/toy/err/legacy_asm_clobber_builtin.expected b/test/toy/err/legacy_asm_clobber_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_clobber_builtin.toy b/test/toy/err/legacy_asm_clobber_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_clobber("", ""); +} diff --git a/test/toy/err/legacy_asm_early_builtin.expected b/test/toy/err/legacy_asm_early_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_early_builtin.toy b/test/toy/err/legacy_asm_early_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_early("", 1, 2); +} diff --git a/test/toy/err/legacy_asm_imm_builtin.expected b/test/toy/err/legacy_asm_imm_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_imm_builtin.toy b/test/toy/err/legacy_asm_imm_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_imm("", 7); +} diff --git a/test/toy/err/legacy_asm_inout_builtin.expected b/test/toy/err/legacy_asm_inout_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_inout_builtin.toy b/test/toy/err/legacy_asm_inout_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_inout("", 1); +} diff --git a/test/toy/err/legacy_asm_int_builtin.expected b/test/toy/err/legacy_asm_int_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_int_builtin.toy b/test/toy/err/legacy_asm_int_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_int("", 1, 2); +} diff --git a/test/toy/err/legacy_asm_mem_builtin.expected b/test/toy/err/legacy_asm_mem_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_mem_builtin.toy b/test/toy/err/legacy_asm_mem_builtin.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + var slot: i64 = 1; + return @asm_mem("", slot); +} diff --git a/test/toy/err/legacy_asm_memory_builtin.expected b/test/toy/err/legacy_asm_memory_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asm_memory_builtin.toy b/test/toy/err/legacy_asm_memory_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asm_memory("", 6); +} diff --git a/test/toy/err/legacy_asmnop_builtin.expected b/test/toy/err/legacy_asmnop_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_asmnop_builtin.toy b/test/toy/err/legacy_asmnop_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @asmnop(); +} diff --git a/test/toy/err/legacy_atomic_builtin.expected b/test/toy/err/legacy_atomic_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_atomic_builtin.toy b/test/toy/err/legacy_atomic_builtin.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @atomic_add(&x, 1); + return x; +} diff --git a/test/toy/err/legacy_byteconst_builtin.expected b/test/toy/err/legacy_byteconst_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_byteconst_builtin.toy b/test/toy/err/legacy_byteconst_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @byteconst(); +} diff --git a/test/toy/err/legacy_fieldtest_builtin.expected b/test/toy/err/legacy_fieldtest_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_fieldtest_builtin.toy b/test/toy/err/legacy_fieldtest_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @fieldtest(); +} diff --git a/test/toy/err/legacy_index_builtin.expected b/test/toy/err/legacy_index_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_index_builtin.toy b/test/toy/err/legacy_index_builtin.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 1; + let p: *i64 = &x; + return @index(p, 0).*; +} diff --git a/test/toy/err/legacy_int_type.expected b/test/toy/err/legacy_int_type.expected @@ -0,0 +1 @@ +legacy int type is unsupported diff --git a/test/toy/err/legacy_int_type.toy b/test/toy/err/legacy_int_type.toy @@ -0,0 +1,3 @@ +fn main(): int { + return 0; +} diff --git a/test/toy/err/legacy_logical_operator.expected b/test/toy/err/legacy_logical_operator.expected @@ -0,0 +1 @@ +legacy logical operator is unsupported diff --git a/test/toy/err/legacy_logical_operator.toy b/test/toy/err/legacy_logical_operator.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + if 1 && 1 { + return 0; + } + return 1; +} diff --git a/test/toy/err/legacy_prefix_deref_assign.expected b/test/toy/err/legacy_prefix_deref_assign.expected @@ -0,0 +1 @@ +expected expression diff --git a/test/toy/err/legacy_prefix_deref_assign.toy b/test/toy/err/legacy_prefix_deref_assign.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + var x: i64 = 1; + let p: *i64 = &x; + *p = 2; + return x; +} diff --git a/test/toy/err/legacy_prefix_deref_expr.expected b/test/toy/err/legacy_prefix_deref_expr.expected @@ -0,0 +1 @@ +expected expression diff --git a/test/toy/err/legacy_prefix_deref_expr.toy b/test/toy/err/legacy_prefix_deref_expr.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 1; + let p: *i64 = &x; + return *p; +} diff --git a/test/toy/err/legacy_target_builtin.expected b/test/toy/err/legacy_target_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_target_builtin.toy b/test/toy/err/legacy_target_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @target(); +} diff --git a/test/toy/err/legacy_target_os_builtin.expected b/test/toy/err/legacy_target_os_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_target_os_builtin.toy b/test/toy/err/legacy_target_os_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @target_os(); +} diff --git a/test/toy/err/legacy_typecheck_builtin.expected b/test/toy/err/legacy_typecheck_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_typecheck_builtin.toy b/test/toy/err/legacy_typecheck_builtin.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @typecheck(); +} diff --git a/test/toy/err/legacy_va_arg_builtin.expected b/test/toy/err/legacy_va_arg_builtin.expected @@ -0,0 +1 @@ +unknown builtin diff --git a/test/toy/err/legacy_va_arg_builtin.toy b/test/toy/err/legacy_va_arg_builtin.toy @@ -0,0 +1,7 @@ +fn pick(n: i64, ...): i64 { + var ap: va_list; + @va_start(ap); + let x: i64 = @va_arg(ap, i64); + @va_end(ap); + return x + n; +} diff --git a/test/toy/err/mixed_precedence_islands.expected b/test/toy/err/mixed_precedence_islands.expected @@ -0,0 +1 @@ +mixed precedence islands require parentheses diff --git a/test/toy/err/mixed_precedence_islands.toy b/test/toy/err/mixed_precedence_islands.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let x: i64 = 1 + 2 << 3; + return x; +} diff --git a/test/toy/err/mutual_by_value_record.expected b/test/toy/err/mutual_by_value_record.expected @@ -0,0 +1 @@ +incomplete record type requires pointer diff --git a/test/toy/err/mutual_by_value_record.toy b/test/toy/err/mutual_by_value_record.toy @@ -0,0 +1,14 @@ +record A; +record B; + +record A { + b: B, +} + +record B { + a: A, +} + +fn main(): i64 { + return 0; +} diff --git a/test/toy/err/nonexhaustive_enum_switch_expression.expected b/test/toy/err/nonexhaustive_enum_switch_expression.expected @@ -0,0 +1 @@ +expression switch requires default diff --git a/test/toy/err/nonexhaustive_enum_switch_expression.toy b/test/toy/err/nonexhaustive_enum_switch_expression.toy @@ -0,0 +1,14 @@ +enum Color: i64 { + .red = 1, + .green = 2, +} + +fn main(): i64 { + let c: Color = .green; + let v: i64 = switch c { + .red { + 10 + } + }; + return v; +} diff --git a/test/toy/err/restrict_non_pointer.expected b/test/toy/err/restrict_non_pointer.expected @@ -0,0 +1 @@ +restrict requires pointer type diff --git a/test/toy/err/restrict_non_pointer.toy b/test/toy/err/restrict_non_pointer.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let x: restrict i64 = 0; + return x; +} diff --git a/test/toy/err/static_local_scope.expected b/test/toy/err/static_local_scope.expected @@ -0,0 +1 @@ +undefined variable diff --git a/test/toy/err/static_local_scope.toy b/test/toy/err/static_local_scope.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + { + var @[.static] hidden: i64 = 1; + } + return hidden; +} diff --git a/test/toy/err/syntax_missing_block_close.expected b/test/toy/err/syntax_missing_block_close.expected @@ -0,0 +1 @@ +expected '}' diff --git a/test/toy/err/syntax_missing_block_close.toy b/test/toy/err/syntax_missing_block_close.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + if 1 { + return 0; +} diff --git a/test/toy/err/tail_return_mismatch.expected b/test/toy/err/tail_return_mismatch.expected @@ -0,0 +1 @@ +tail call signature mismatch diff --git a/test/toy/err/tail_return_mismatch.toy b/test/toy/err/tail_return_mismatch.toy @@ -0,0 +1,7 @@ +fn sink(x: i64): i32 { + return x as i32; +} + +fn main(): i64 { + return tail sink(42); +} diff --git a/test/toy/err/tail_variadic.expected b/test/toy/err/tail_variadic.expected @@ -0,0 +1 @@ +tail call to variadic function unsupported diff --git a/test/toy/err/tail_variadic.toy b/test/toy/err/tail_variadic.toy @@ -0,0 +1,7 @@ +fn sink(x: i64, ...): i64 { + return x; +} + +fn main(): i64 { + return tail sink(42); +} diff --git a/test/toy/err/type_mismatch.expected b/test/toy/err/type_mismatch.expected @@ -0,0 +1 @@ +return type mismatch diff --git a/test/toy/err/type_mismatch.toy b/test/toy/err/type_mismatch.toy @@ -0,0 +1,3 @@ +fn main(): i32 { + return 1; +} diff --git a/test/toy/err/unknown_decl_attribute.expected b/test/toy/err/unknown_decl_attribute.expected @@ -0,0 +1 @@ +unknown attribute diff --git a/test/toy/err/unknown_decl_attribute.toy b/test/toy/err/unknown_decl_attribute.toy @@ -0,0 +1,4 @@ +fn @[.not_a_real_attribute] +main(): i64 { + return 0; +} diff --git a/test/toy/err/unknown_enum_value.expected b/test/toy/err/unknown_enum_value.expected @@ -0,0 +1 @@ +unknown enum value diff --git a/test/toy/err/unknown_enum_value.toy b/test/toy/err/unknown_enum_value.toy @@ -0,0 +1,8 @@ +enum Color: i64 { + .red = 1, +} + +fn main(): i64 { + let c: Color = .blue; + return c as i64; +} diff --git a/test/toy/err/unknown_field_attribute.expected b/test/toy/err/unknown_field_attribute.expected @@ -0,0 +1 @@ +unknown field attribute diff --git a/test/toy/err/unknown_field_attribute.toy b/test/toy/err/unknown_field_attribute.toy @@ -0,0 +1,7 @@ +record Bad { + value @[.not_a_field_attribute]: i64, +} + +fn main(): i64 { + return 0; +} diff --git a/test/toy/err/unknown_record_attribute.expected b/test/toy/err/unknown_record_attribute.expected @@ -0,0 +1 @@ +unknown record attribute diff --git a/test/toy/err/unknown_record_attribute.toy b/test/toy/err/unknown_record_attribute.toy @@ -0,0 +1,7 @@ +record @[.not_a_record_attribute] Bad { + value: i64, +} + +fn main(): i64 { + return 0; +} diff --git a/test/toy/err/unsupported_coro_switch.expected b/test/toy/err/unsupported_coro_switch.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_coro_switch.toy b/test/toy/err/unsupported_coro_switch.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var from: *void = NULL as *void; + var to: *void = NULL as *void; + return @coro_switch<i64>(from, to, 0); +} diff --git a/test/toy/err/unsupported_cpu_nop.expected b/test/toy/err/unsupported_cpu_nop.expected @@ -0,0 +1 @@ +unsupported intrinsic diff --git a/test/toy/err/unsupported_cpu_nop.toy b/test/toy/err/unsupported_cpu_nop.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @cpu_nop(); + return 42; +} diff --git a/test/toy/err/unsupported_dcache_clean.expected b/test/toy/err/unsupported_dcache_clean.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_dcache_clean.toy b/test/toy/err/unsupported_dcache_clean.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @dcache_clean(&x, 8); + return 0; +} diff --git a/test/toy/err/unsupported_dcache_clean_invalidate.expected b/test/toy/err/unsupported_dcache_clean_invalidate.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_dcache_clean_invalidate.toy b/test/toy/err/unsupported_dcache_clean_invalidate.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @dcache_clean_invalidate(&x, 8); + return 0; +} diff --git a/test/toy/err/unsupported_dcache_invalidate.expected b/test/toy/err/unsupported_dcache_invalidate.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_dcache_invalidate.toy b/test/toy/err/unsupported_dcache_invalidate.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @dcache_invalidate(&x, 8); + return 0; +} diff --git a/test/toy/err/unsupported_dmb.expected b/test/toy/err/unsupported_dmb.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_dmb.toy b/test/toy/err/unsupported_dmb.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @dmb(.inner); + return 0; +} diff --git a/test/toy/err/unsupported_dsb.expected b/test/toy/err/unsupported_dsb.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_dsb.toy b/test/toy/err/unsupported_dsb.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @dsb(.full); + return 0; +} diff --git a/test/toy/err/unsupported_icache_invalidate.expected b/test/toy/err/unsupported_icache_invalidate.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_icache_invalidate.toy b/test/toy/err/unsupported_icache_invalidate.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var x: i64 = 0; + @icache_invalidate(&x, 8); + return 0; +} diff --git a/test/toy/err/unsupported_irq_restore.expected b/test/toy/err/unsupported_irq_restore.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_irq_restore.toy b/test/toy/err/unsupported_irq_restore.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @irq_restore(0); + return 0; +} diff --git a/test/toy/err/unsupported_irq_save.expected b/test/toy/err/unsupported_irq_save.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_irq_save.toy b/test/toy/err/unsupported_irq_save.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + let flags: usize = @irq_save(); + return flags as i64; +} diff --git a/test/toy/err/unsupported_longjmp.expected b/test/toy/err/unsupported_longjmp.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_longjmp.toy b/test/toy/err/unsupported_longjmp.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var buf: [8]i64 = []; + @longjmp(&buf, 1); + return 0; +} diff --git a/test/toy/err/unsupported_setjmp.expected b/test/toy/err/unsupported_setjmp.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_setjmp.toy b/test/toy/err/unsupported_setjmp.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + var buf: [8]i64 = []; + return @setjmp(&buf) as i64; +} diff --git a/test/toy/err/unsupported_sev.expected b/test/toy/err/unsupported_sev.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_sev.toy b/test/toy/err/unsupported_sev.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @sev(); + return 0; +} diff --git a/test/toy/err/unsupported_syscall.expected b/test/toy/err/unsupported_syscall.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_syscall.toy b/test/toy/err/unsupported_syscall.toy @@ -0,0 +1,3 @@ +fn main(): i64 { + return @syscall(1, 0); +} diff --git a/test/toy/err/unsupported_wfe.expected b/test/toy/err/unsupported_wfe.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_wfe.toy b/test/toy/err/unsupported_wfe.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @wfe(); + return 0; +} diff --git a/test/toy/err/unsupported_wfi.expected b/test/toy/err/unsupported_wfi.expected @@ -0,0 +1 @@ +unsupported target intrinsic diff --git a/test/toy/err/unsupported_wfi.toy b/test/toy/err/unsupported_wfi.toy @@ -0,0 +1,4 @@ +fn main(): i64 { + @wfi(); + return 0; +} diff --git a/test/toy/err/use_function_before_decl.expected b/test/toy/err/use_function_before_decl.expected @@ -0,0 +1 @@ +undefined function diff --git a/test/toy/err/use_function_before_decl.toy b/test/toy/err/use_function_before_decl.toy @@ -0,0 +1,7 @@ +fn main(): i64 { + return helper(); +} + +fn helper(): i64 { + return 42; +} diff --git a/test/toy/err/use_type_before_decl.expected b/test/toy/err/use_type_before_decl.expected @@ -0,0 +1 @@ +expected type diff --git a/test/toy/err/use_type_before_decl.toy b/test/toy/err/use_type_before_decl.toy @@ -0,0 +1,9 @@ +fn main(): i64 { + let p: LaterPair = LaterPair { a: 40, b: 2 }; + return p.a + p.b; +} + +record LaterPair { + a: i64, + b: i64, +} diff --git a/test/toy/run.sh b/test/toy/run.sh @@ -8,6 +8,9 @@ # # Sidecars: # <name>.expected expected process exit code, default 0 +# <name>.objdump fixed substrings expected in `cfree objdump -h -t` +# after the linked-object compile path +# err/<name>.expected expected diagnostic substring for compile-fail cases # # Filtering: # ./run.sh [name_filter] [paths] @@ -91,7 +94,8 @@ run_case_link() { local name="$1" src="$2" expected="$3" work="$4" opt="$5" local obj="$work/$name.o" exe="$work/$name.exe" local cc_err="$work/cc.err" ld_err="$work/ld.err" out="$work/exe.out" - local err="$work/exe.err" rc + local err="$work/exe.err" dump="$work/objdump.out" dump_err="$work/objdump.err" rc + local dump_exp="${src%.toy}.objdump" if ! "$CFREE" cc "-O$opt" -c "$src" -o "$obj" > "$work/cc.out" 2> "$cc_err"; then note_fail "$name/L-O$opt" @@ -106,6 +110,32 @@ run_case_link() { return fi + if [ -f "$dump_exp" ]; then + if ! "$CFREE" objdump -h -t "$obj" > "$dump" 2> "$dump_err"; then + note_fail "$name/L-O$opt:objdump" + printf ' cfree objdump failed\n' + sed 's/^/ | /' "$dump_err" + return + fi + missing=0 + while IFS= read -r pattern || [ -n "$pattern" ]; do + [ -z "$pattern" ] && continue + if ! grep -F -q -- "$pattern" "$dump"; then + missing=1 + printf '%s\n' "$pattern" >> "$work/objdump.missing" + fi + done < "$dump_exp" + if [ "$missing" -eq 0 ]; then + note_pass "$name/L-O$opt:objdump" + else + note_fail "$name/L-O$opt:objdump" + printf ' missing objdump substring(s):\n' + sed 's/^/ > /' "$work/objdump.missing" + printf ' actual objdump:\n' + sed 's/^/ | /' "$dump" + fi + fi + if ! "$CFREE" ld "$obj" -o "$exe" > "$work/ld.out" 2> "$ld_err"; then note_fail "$name/L-O$opt" printf ' cfree ld failed\n' @@ -312,6 +342,40 @@ for src in "${cases[@]}"; do done done +err_cases=("$TEST_DIR"/err/*.toy) +for src in "${err_cases[@]}"; do + name="$(basename "$src" .toy)" + if [ -n "$FILTER" ] && [[ "$name" != *"$FILTER"* ]]; then + continue + fi + work="$BUILD_DIR/err/$name" + rm -rf "$work" + mkdir -p "$work" + obj="$work/$name.o" + out="$work/cc.out" + err="$work/cc.err" + exp="${src%.toy}.expected" + if "$CFREE" cc -c "$src" -o "$obj" > "$out" 2> "$err"; then + note_fail "$name/E" + printf ' expected compile failure, got success\n' + continue + fi + if [ ! -f "$exp" ]; then + note_fail "$name/E" + printf ' missing expected diagnostic: %s\n' "$exp" + continue + fi + if grep -F -q -f "$exp" "$err"; then + note_pass "$name/E" + else + note_fail "$name/E" + printf ' expected diagnostic substring:\n' + sed 's/^/ > /' "$exp" + printf ' actual stderr:\n' + sed 's/^/ | /' "$err" + fi +done + printf '\nResults: %d pass, %d fail, %d skip\n' "$PASS" "$FAIL" "$SKIP" if [ $FAIL -ne 0 ]; then printf 'Failures:\n'