diff --git a/README.md b/README.md index c81762d..3d0b699 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,38 @@ Options: ## Configuration -`c3fmt` will try to find a `.c3fmt` format inside the working directory. You can also -pass your own path to `c3fmt`, or force the default configuration. +`c3fmt` will try to find a `.c3fmt` configuration file inside the working directory. You can also pass your own path to `c3fmt` using the `--config` flag, or force the default configuration with `--default`. You can look at [.c3fmt](.c3fmt) for the default configuration. + +### Available Options + +| Key | Description | Default | +| --- | --- | --- | +| `use_tabs` | Use tabs for indentation. | `true` | +| `tab_size` | The width of a tab character. | `4` | +| `indent_width` | The number of spaces to use for indentation (if `use_tabs` is false). | `4` | +| `max_blank_line_between_statements` | Maximum number of blank lines to preserve between statements. | `2` | +| `max_line_length` | Maximum line length before wrapping. | `120` | +| `brace_style` | The brace style to use: `ALLMAN` or `K&R`. | `ALLMAN` | +| `else_on_newline` | Whether to put `else` on a new line. | `true` | +| `align_assignments` | Align `=` and `=>` in consecutive declarations/assignments. | `true` | +| `align_comments` | Align trailing comments in consecutive lines. | `true` | ## Building -Building requires the [C3 compiler](https://c3-lang.org/) and the [tree-sitter](https://github.com/tree-sitter/tree-sitter) SDK library. +Building requires the [C3 compiler](https://c3-lang.org/) and the [tree-sitter](https://github.com/tree-sitter/tree-sitter) SDK library. For instructions on how to build and install the tree-sitter library, refer to the [tree-sitter getting started guide](https://tree-sitter.github.io/tree-sitter/using-parsers/1-getting-started.html). To build the executable: ```bash c3c build ``` + +If the `tree-sitter` library is not in your system's default search path, you can specify the path using the `-L` flag: + +```bash +c3c build -L /path/to/tree-sitter/lib +``` + The binary will be located in `build/c3fmt`. ### Updating Sources @@ -66,6 +86,11 @@ Run all tests using the C3 compiler: c3c test ``` +Just like with the build command, if `tree-sitter` is not in your search path, use the `-L` flag: +```bash +c3c test -L /path/to/tree-sitter/lib +``` + The test suite includes: - **Corpus**: Compares formatted output against expected `_f.c3` files. - **Stability**: Ensures that formatting already-formatted code produces no changes (idempotency). diff --git a/lib/tree_sitter_c3.c3l b/lib/tree_sitter_c3.c3l index 34a4bbd..2d7cc8c 100644 Binary files a/lib/tree_sitter_c3.c3l and b/lib/tree_sitter_c3.c3l differ diff --git a/scripts/update_tree_sitter_c3.sh b/scripts/update_tree_sitter_c3.sh index 57638a3..dee677f 100755 --- a/scripts/update_tree_sitter_c3.sh +++ b/scripts/update_tree_sitter_c3.sh @@ -11,7 +11,7 @@ set -euo pipefail -REF="${1:-main}" +REF="${1:-c3-0.8}" REPO="https://github.com/c3lang/tree-sitter-c3" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -82,4 +82,8 @@ echo "Packing .c3l ..." # install cp "$STAGING/c3l/tree_sitter_c3.c3l" "$LIB_DIR/tree_sitter_c3.c3l" + +echo "Clearing compiler caches to ensure library reload ..." +rm -rf "$SCRIPT_DIR/../build" "$HOME/.c3" + echo "Done. lib/tree_sitter_c3.c3l updated from $REPO @ $REF" diff --git a/src/config.c3 b/src/config.c3 index f303ada..0a78314 100644 --- a/src/config.c3 +++ b/src/config.c3 @@ -107,6 +107,7 @@ fn Config? parse_from_file(String file_path) => @pool() else if (val == "K&R") { conf.brace_style = K_AND_R; + conf.else_on_newline = false; } else { diff --git a/src/formatter.c3 b/src/formatter.c3 index 74c8c18..3e39f2f 100644 --- a/src/formatter.c3 +++ b/src/formatter.c3 @@ -216,7 +216,7 @@ fn bool C3Fmt.reattach_comment(&self, TSNode child, int blank_line_amount, bool } self.buf.space(); - self.process_comment(child); + self.process_comment(child, is_trailing: true); self.buf.newline(); if (repair_indent) @@ -232,7 +232,7 @@ fn bool C3Fmt.reattach_comment(&self, TSNode child, int blank_line_amount, bool return false; } self.buf.space(); - self.process_comment(child); + self.process_comment(child, is_trailing: true); self.buf.newline(); } @@ -246,18 +246,53 @@ fn bool C3Fmt.reattach_comment(&self, TSNode child, int blank_line_amount, bool if (blank_line_amount == 1 && popped_indent) // restick to previous uncanny stmt { - self.process_comment(child); + self.process_comment(child, is_trailing: true); self.buf.newline(); self.dedent(); return true; } - if (popped_indent) + return false; +} + +fn void C3Fmt.open_brace(&self, bool force_newline = false) +{ + if (self.config.brace_style == K_AND_R && !force_newline) + { + if (self.buf.gobble_last_newline()) + { + self.buf.space(); + } + else + { + Token? last = self.buf.last(); + if (try l = last) + { + if (l.kind != TokenKind.SPACE) + { + self.buf.space(); + } + } + else + { + self.buf.space(); + } + } + } + else { - self.dedent(); // Revert + self.buf.newline(); } - return false; + self.buf.text("{"); + self.buf.newline(); + self.indent(); +} + +fn void C3Fmt.close_brace(&self) +{ + self.dedent(); + self.buf.text("}"); } faultdef NOT_FOUND; @@ -291,11 +326,10 @@ fn bool is_alignable_block_node(TSNode node) if (type == "declaration_stmt" || type == "global_declaration") { if (ts::node_named_child_count(node) == 0) return false; - TSNode inner = ts::node_named_child(node, 0); - String inner_type = inner.type(); - return inner_type == "const_declaration" || inner_type == "var_declaration"; + node = ts::node_named_child(node, 0); + type = node.type(); } - return type == "const_declaration" || type == "var_declaration" || type == "macro_declaration" || type == "enum_constant"; + return type == "const_declaration" || type == "var_declaration" || (type == "declaration" && node.start_point_row() == node.end_point_row()) || type == "enum_constant"; } fn TSNode find_assignment_op(TSNode node) @@ -467,10 +501,13 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint continue; } if (ts::node_eq(c, right)) continue; - if (c.type() == ";") { - self.buf.push(token::text(";")); - } else if (ts::node_is_named(c)) { - self.process_node(c); + if (ts::node_is_named(c)) { + if (c.is_comment()) { + self.buf.push(token::space()); + self.buf.push(token::text(c.get_text(self.source_code))); + } else { + self.process_node(c, false); + } } else { self.buf.push(token::text(c.get_text(self.source_code))); } @@ -480,7 +517,16 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint if (child.type() == "declaration_stmt" || child.type() == "global_declaration") { for (uint s = 1; s < ts::node_child_count(child); ++s) { TSNode sc = ts::node_child(child, s); - if (sc.type() == ";") self.buf.push(token::text(";")); + if (ts::node_is_named(sc)) { + if (sc.is_comment()) { + self.buf.push(token::space()); + self.buf.push(token::text(sc.get_text(self.source_code))); + } else { + self.process_node(sc, false); + } + } else { + self.buf.push(token::text(sc.get_text(self.source_code))); + } } } @@ -488,29 +534,29 @@ fn void C3Fmt.process_aligned_block(&self, TSNode parent, uint start_index, uint { self.buf.text(","); } + } + else + { + self.process_node(child, false); + } - // Handle trailing comment alignment - if (i + 1 < named_child_count) + // Handle trailing comment alignment + if (i + 1 < named_child_count) + { + TSNode next = ts::node_named_child(parent, i + 1); + if (next.start_point_row() == child.end_point_row() && next.is_comment()) { - TSNode next = ts::node_named_child(parent, i + 1); - if (next.start_point_row() == child.end_point_row() && next.is_comment()) + if (self.config.align_comments) { - if (self.config.align_comments) - { - self.buf.push(token::align(max_comment_col)); - } - self.buf.push(token::space()); - self.process_comment(next); - i++; // Skip comment in next iteration + self.buf.push(token::align(max_comment_col)); } + self.buf.push(token::space()); + self.process_comment(next, is_trailing: true); + i++; // Skip comment in next iteration } - - self.buf.push(token::newline()); - } - else - { - self.process_node(child, true); } + + self.buf.push(token::newline()); i++; } } @@ -789,7 +835,7 @@ fn void C3Fmt.process_uncanny_scope( if (next.is_comment() && ts::same_line(child, next)) { self.buf.space(); - self.process_comment(next); + self.process_comment(next, is_trailing: true); i++; // advance } } @@ -903,7 +949,7 @@ fn void C3Fmt.process_ct_case_stmt(&self, TSNode node) )) // as trailing { self.buf.space(); - self.process_line_comment(child); + self.process_line_comment(child, is_trailing: true); self.buf.newline(); nl_appended = true; } @@ -1005,10 +1051,7 @@ fn void C3Fmt.process_asm_block_stmt(&self, TSNode node) if (type == "{") { // TODO - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); in_scope = true; in_scope_id = i; @@ -1026,8 +1069,7 @@ fn void C3Fmt.process_asm_block_stmt(&self, TSNode node) { if (type == "}") { - self.dedent(); - self.buf.text("}"); + self.close_brace(); self.buf.newline(); return; // TODO assert we are finished @@ -1090,7 +1132,7 @@ fn String C3Fmt.extract_comment_string(&self, TSNode comment) Pretty print a comment. @param node : "The processed node" *> -fn void C3Fmt.process_comment(&self, TSNode node) +fn void C3Fmt.process_comment(&self, TSNode node, bool is_trailing = false) { assert (node.is_comment()); @@ -1100,7 +1142,7 @@ fn void C3Fmt.process_comment(&self, TSNode node) } if (node.type() == "line_comment") { - self.process_line_comment(node); + self.process_line_comment(node, is_trailing); } } @@ -1131,14 +1173,17 @@ fn void C3Fmt.process_block_comment(&self, TSNode node) Pretty print a block comment. @param node : "The processed node" *> -fn void C3Fmt.process_line_comment(&self, TSNode node) +fn void C3Fmt.process_line_comment(&self, TSNode node, bool is_trailing = false) { - TSNode content = ts::node_named_child(node, 0); - self.buf.text(node.get_text(self.source_code)[0:2]); + // Trailing comments (on same line as code): normalize to single space. + // Standalone comments (own line): preserve internal indentation after the separator space. + String body = is_trailing + ? self.extract_comment_string(node) + : node.get_text(self.source_code)[2..].strip_prefix(" ").trim_right(); self.buf.space(); - String comment = self.extract_comment_string(node); - self.buf.text(comment); + self.buf.text(body); + String comment = body.trim(); if (comment == "c3fmt on") { @@ -1344,7 +1389,35 @@ fn void C3Fmt.process_node( } }, group_id: id); case "param": - self.process_composite(node, skip_space_if_after: { "&" },); + // Special handling for "...": + // - Typed: "int ...args" (space before "...", no space after) + // - Untyped: "args..." (no space before "...", nothing after) + for (uint i = 0; i < ts::node_child_count(node); ++i) + { + TSNode child = ts::node_child(node, i); + String type = child.type(); + String prev_type = i > 0 ? ts::node_child(node, i - 1).type() : ""; + String prev_field = i > 0 ? ts::node_child(node, i - 1).field_name() : ""; + + if (i > 0 && type != "..." && prev_type != "..." && type != "&" && prev_type != "&") + { + self.buf.space(); + } + if (type == "..." && prev_field == "type") self.buf.space(); + + if (child.is_comment()) + { + self.process_comment(child); + } + else if (ts::node_is_named(child)) + { + self.process_node(child); + } + else + { + self.buf.text(child.get_text(self.source_code)); + } + } case "param_default": self.process_composite(node); case "lambda_declaration": @@ -1397,13 +1470,9 @@ fn void C3Fmt.process_node( switch (child.type()) { case "compound_stmt": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(child); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "implies_body": self.buf.space(); self.process_composite(node, append_newline: false); @@ -1431,13 +1500,9 @@ fn void C3Fmt.process_node( append_newline: true ); case "struct_body": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "struct_member_declaration": self.process_composite(node, append_newline: true); case "enum_declaration": @@ -1455,13 +1520,9 @@ fn void C3Fmt.process_node( case "enum_spec": self.process_composite(node); case "enum_body": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "enum_constant": self.process_composite(node); @@ -1494,7 +1555,7 @@ fn void C3Fmt.process_node( self.buf.text(","); } - self.buf.newline(); + if (inside_compound) self.buf.newline(); case "enum_arg": self.process_composite(node); case "enum_param_list": @@ -1512,13 +1573,9 @@ fn void C3Fmt.process_node( append_newline: true ); case "bitstruct_body": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "bitstruct_member_declaration": self.process_composite( node, @@ -1533,13 +1590,9 @@ fn void C3Fmt.process_node( append_newline: true ); case "interface_body": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "faultdef_declaration": uint start_row = node.start_point().row; for (uint i = 0; i < ts::node_child_count(node); ++i) @@ -1848,7 +1901,7 @@ fn void C3Fmt.process_node( skip_space_if_after: { "<" }, ); case "initializer_element": - self.process_composite(node); + self.process_composite(node, skip_space_if_after: { "..." }); case "param_path": self.process_sequence(node); case "typed_initializer_list": @@ -1897,19 +1950,9 @@ fn void C3Fmt.process_node( /* STATEMENTS & CONTROL FLOW */ /* ======================================== */ case "compound_stmt": - if (!inside_compound) - { - // shitty workaround to handle compound stmt - // for scope decl - self.buf.newline(); - } - - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(force_newline: inside_compound); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); if (inside_compound) { @@ -1998,7 +2041,7 @@ fn void C3Fmt.process_node( && !node.has_child_of_type("else_part") ); case "else_part": - if (self.config.else_on_newline) + if (self.config.else_on_newline && self.config.brace_style != K_AND_R) { self.buf.newline(); } @@ -2013,13 +2056,9 @@ fn void C3Fmt.process_node( append_newline: true ); case "switch_body": - self.buf.newline(); - self.buf.text("{"); - self.buf.newline(); - self.indent(); + self.open_brace(); self.process_compound(node); - self.dedent(); - self.buf.text("}"); + self.close_brace(); case "case_stmt": self.process_uncanny_scope(node, ":"); case "default_stmt": @@ -2045,12 +2084,20 @@ fn void C3Fmt.process_node( skip_space_if_after: { "[" } ); case "ct_assert_stmt": + case "ct_error_stmt": self.process_composite( node, skip_space_type: { ";", ")", "," }, skip_space_if_after: { "(" }, append_newline: true ); + case "ct_expand_stmt": + self.process_composite( + node, + skip_space_type: { ";", ")" }, + skip_space_if_after: { "(" }, + append_newline: true + ); case "ct_echo_stmt": self.process_composite(node, append_newline: true); case "ct_include_stmt": @@ -2164,6 +2211,10 @@ fn void C3Fmt.process_node( case "bytes_expr": // TODO idk what this is, a space separated list of bytes_literal ? case "string_expr": // same... + case "lengthof": + case "$eval": + case "$stringify": + case "$reflect": self.process_composite(node); // Known for not working, waiting for https://github.com/c3lang/tree-sitter-c3/issues/50 @@ -2172,7 +2223,7 @@ fn void C3Fmt.process_node( self.process_composite(node); case "declaration_stmt": case "global_declaration": - self.process_composite(node, append_newline: true); + self.process_composite(node, append_newline: inside_compound); default: io::eprintfn( diff --git a/src/main.c3 b/src/main.c3 index b57b838..8d53774 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -130,8 +130,8 @@ fn int main(String[] args) if (version_asked) { - io::printfn("c3fmt version: \t0.2.3"); - io::printfn("target c3c version:\t0.7.11"); + io::printfn("c3fmt version: \t0.2.4"); + io::printfn("target c3c version:\t0.8.0"); return 0; } @@ -355,9 +355,9 @@ fn void? format_file(String path, Config config, Context* ctx) io::eprintfn("Format mismatch: %s", path); // Show diff if possible - if (try p = process::create({"diff", "--unified=0", "-p", "--color=always", "--label", path, "--label", "formatted", path, "-"}, { .search_user_path = true })) + if (try p = process::spawn({"diff", "--unified=0", "-p", "--color=always", "--label", path, "--label", "formatted", path, "-"})) { - (void)p.write_to_stdin(formatted); + (void)p.write_stdin(formatted); // Close stdin to signal end of input libc::fclose(p.stdin_file); p.stdin_file = null; diff --git a/src/opt.c3 b/src/opt.c3 index 426d3e8..6397fb8 100644 --- a/src/opt.c3 +++ b/src/opt.c3 @@ -57,7 +57,7 @@ faultdef INVALID_WSTRING, INVALID_FORMAT; -constdef GetoptPrefixType : inline int @local +constdef GetoptPrefixType : inline int @local @constinit { NO_PREFIX = -1, D_PREFIX = 0, @@ -68,7 +68,7 @@ constdef GetoptPrefixType : inline int @local const char[1] PLACEHOLDER @local = {0}; // used to mark an empty 'place' value -enum OptionArgType : inline int +enum OptionArgType : int { NO_ARGUMENT, REQUIRED_ARGUMENT, @@ -604,23 +604,24 @@ macro int? @parse(#args, ...) => @pool() $endif // The third param MUST be a pointer/reference or function type. - $assert @in($kindof(assignee), POINTER, FUNC) - : @sprintf("Assigned parameter from args triple #%s: type `%s` is not a pointer or function type.", $idx + 1, $typeof(assignee).nameof); + $assert @in(@kindof(assignee), TypeKind.POINTER, TypeKind.FUNC) + : @sprintf("Assigned parameter from args triple #%s: type `%s` is not a pointer or function type.", $idx + 1, $typeof(assignee)::name); OptionArgType $has_arg = NO_ARGUMENT; // default - $if $kindof(assignee) == FUNC ||| $typeof(assignee).inner.kindof != BOOL: // Only 'bool*' types represent flags, which do not have arguments. + var $AssigneeInner = $typeof(assignee)::inner; + $if @kindof(assignee) == TypeKind.FUNC ||| $AssigneeInner::kind != TypeKind.BOOL: // Only 'bool*' types represent flags, which do not have arguments. $switch: $case ($longname.len >= 2 &&& $longname[^1] == '?') ||| ($shortname.len >= 2 &&& $shortname[1] == '?'): $has_arg = OPTIONAL_ARGUMENT; $case ($longname.len >= 2 &&& $longname[^1] == '+') ||| ($shortname.len >= 2 &&& $shortname[1] == '+'): $has_arg = INCREMENTAL; // incremental types do not accept arguments $case ($longname.len >= 2 &&& $longname[^1] == '*') ||| ($shortname.len >= 2 &&& $shortname[1] == '*'): $has_arg = REQUEST_HELP; // nor do help types - $case $kindof(assignee) == FUNC: $has_arg = CALLBACK; + $case @kindof(assignee) == TypeKind.FUNC: $has_arg = CALLBACK; $default: $has_arg = REQUIRED_ARGUMENT; $endswitch $if $has_arg == INCREMENTAL: $assert $defined(*assignee) &&& $defined((*assignee)++) : @sprintf("Incremental option '%s' must be a pointer to an incrementable (++ capable) type, but got '%s' instead", - $shortname[^1] == '+' ? $shortname : $longname, $typeof(assignee).nameof); + $shortname[^1] == '+' ? $shortname : $longname, $typeof(assignee)::name); $endif $if $shortname.len > 0: // only do this when building a dynamic shortopts string @@ -709,7 +710,7 @@ macro int? @parse(#args, ...) => @pool() $case OptionArgType.CALLBACK: if (null == opt::arg.ptr || !opt::arg.len) return MISSING_ARGUMENT~; // callbacks MUST have an argument to pass $assert $defined(OptionCallback z = assignee) - : @sprintf("Callback argument types must provide a function reference of type `OptionCallback`, but got `%s`.", $typeof(assignee).nameof); + : @sprintf("Callback argument types must provide a function reference of type `OptionCallback`, but got `%s`.", $typeof(assignee)::name); assignee(opt::arg)!; // invoke the callback method, bubbling up any potential fault $default: @@ -718,29 +719,29 @@ macro int? @parse(#args, ...) => @pool() if ($ct_argstype[$idx] == OPTIONAL_ARGUMENT) break CT_SWITCH; // optional arg? continue checking options return MISSING_ARGUMENT~; // required arg? throw } - $assert $kindof(assignee) == POINTER &&& $defined($typeof(assignee).inner) - : "The assignee value must be a pointer type and must have an '.inner' property"; + $assert @kindof(assignee) == TypeKind.POINTER &&& $defined($typeof(assignee)::inner) + : "The assignee value must be a pointer type and must have an '::inner' property"; // Set the passed pointer's value. String arg = opt::arg; var $ArgType = $typeof(assignee); - var $SingleDerefArgType = $ArgType.inner; - var $DerefArgType = $ArgType.inner; // keep digging until the type is no longer a pointer kind - $for ; $DerefArgType.kindof == TypeKind.POINTER; $DerefArgType = $DerefArgType.inner: $endfor + var $SingleDerefArgType = $ArgType::inner; + var $DerefArgType = $ArgType::inner; // keep digging until the type is no longer a pointer kind + $for ; $DerefArgType::kind == TypeKind.POINTER; $DerefArgType = $DerefArgType::inner: $endfor - $switch $typeof(assignee).inner.kindof: + $switch $typeof(*assignee)::kind: $case ARRAY: $assert $defined($typeof((*assignee)[0])) : "The ARRAY kindof arg must be indexable"; $ArgType = $typeof(assignee); $SingleDerefArgType = $typeof((*assignee)[0]); $DerefArgType = $SingleDerefArgType; // keep digging until the type is no longer a pointer kind - $for ; $DerefArgType.kindof == TypeKind.POINTER; $DerefArgType = $DerefArgType.inner: $endfor + $for ; $DerefArgType::kind == TypeKind.POINTER; $DerefArgType = $DerefArgType::inner: $endfor ushort current_index = array_storage_indexes[(char)$shortname]++; if (current_index >= (*assignee).len) return OUT_OF_BOUNDS~; - var res = @derive_arg_value(arg, $DerefArgType.typeid, $DerefArgType.typeid, $ArgType.typeid); + var res = @derive_arg_value(arg, $DerefArgType::typeid, $DerefArgType::typeid, $ArgType::typeid); $if $defined(res!!): if (catch err = res) return err~; $endif (*assignee)[current_index] = ($DerefArgType)res; @@ -751,7 +752,7 @@ macro int? @parse(#args, ...) => @pool() $DerefArgType enum_val; if (catch @try(enum_val, enum_by_name($DerefArgType, arg.to_upper_copy(tmem)))) // uppercase because C3 naming rules { - io::eprintf("Failed to convert argument `%s` to enum type `%s`.", arg, $DerefArgType.nameof); + io::eprintf("Failed to convert argument `%s` to enum type `%s`.", arg, $DerefArgType::name); return INVALID_ENUM_VALUE~; } *assignee = enum_val; @@ -762,7 +763,7 @@ macro int? @parse(#args, ...) => @pool() } $default: // in the default case, we let the language allow/disallow the assignment - var res = @derive_arg_value(arg, $SingleDerefArgType.typeid, $DerefArgType.typeid, $ArgType.typeid); + var res = @derive_arg_value(arg, $SingleDerefArgType::typeid, $DerefArgType::typeid, $ArgType::typeid); $if $defined(res!!): if (catch err = res) return err~; $endif // $if $defined(res!!): return res; $endif *assignee = ($SingleDerefArgType)res; @@ -787,30 +788,30 @@ macro int? @parse(#args, ...) => @pool() macro @derive_arg_value(String arg, $SingleDerefArgType, $DerefArgType, $ArgType) @local { $switch ($SingleDerefArgType): - $case int128.typeid: - $case long.typeid: - $case int.typeid: - $case short.typeid: - $case ichar.typeid: - $case uint128.typeid: - $case ulong.typeid: - $case uint.typeid: - $case ushort.typeid: + $case int128::typeid: + $case long::typeid: + $case int::typeid: + $case short::typeid: + $case ichar::typeid: + $case uint128::typeid: + $case ulong::typeid: + $case uint::typeid: + $case ushort::typeid: $DerefArgType? retval = arg.to_integer($DerefArgType); if (catch oops = retval) { - io::eprintf("Exception caught while parsing arg '%s' as NUMERIC type '%s': %s", arg, $DerefArgType.nameof, oops); + io::eprintf("Exception caught while parsing arg '%s' as NUMERIC type '%s': %s", arg, $DerefArgType::name, oops); return INVALID_INTEGER~; } return retval; - $case char.typeid: + $case char::typeid: if (ascii::@is_digit(arg[0])) { $DerefArgType? retval = arg.to_integer($DerefArgType); if (catch oops = retval) { - io::eprintf("Exception caught while parsing arg '%s' as CHAR type '%s': %s", arg, $DerefArgType.nameof, oops); + io::eprintf("Exception caught while parsing arg '%s' as CHAR type '%s': %s", arg, $DerefArgType::name, oops); return INVALID_CHAR~; } return retval; @@ -826,17 +827,17 @@ macro @derive_arg_value(String arg, $SingleDerefArgType, $DerefArgType, $ArgType return arg[0]; } - $case float.typeid: return arg.to_float()!!; - $case double.typeid: return arg.to_double()!!; + $case float::typeid: return arg.to_float()!!; + $case double::typeid: return arg.to_double()!!; - $case String.typeid: - $case ZString.typeid: - $case (char*).typeid: - $case (char[*]*).typeid: - $case char[*].typeid: + $case String::typeid: + $case ZString::typeid: + $case char*::typeid: + $case char[*]*::typeid: + $case char[*]::typeid: return ($SingleDerefArgType)arg; - $case WString.typeid: + $case WString::typeid: WString? retval = arg.to_wstring(); if (catch oops = retval) { @@ -846,6 +847,6 @@ macro @derive_arg_value(String arg, $SingleDerefArgType, $DerefArgType, $ArgType return retval; $default: - $error @sprintf("Type `%s` could not be derived from a String argument.", $ArgType.nameof); + $error @sprintf("Type `%s` could not be derived from a String argument.", $ArgType::name); $endswitch } diff --git a/test/corpus.c3 b/test/corpus.c3 index 0bd1c5f..e3b3e50 100644 --- a/test/corpus.c3 +++ b/test/corpus.c3 @@ -80,4 +80,20 @@ fn void indent_leak() @test { fn void generics() @test { test("generics", "test/corpus/generics.c3", "test/corpus/generics_f.c3"); -} \ No newline at end of file +} + +fn void knr_braces() @test { + Config conf = c3fmt::default_config(); + conf.brace_style = K_AND_R; + test("knr_braces", "test/corpus/knr_braces.c3", "test/corpus/knr_braces_f.c3", conf); +} + +fn void varargs() @test { + test("varargs", "test/corpus/varargs.c3", "test/corpus/varargs_f.c3"); +} + +fn void comment_indent() @test { + Config conf = c3fmt::default_config(); + conf.align_comments = true; + test("comment_indent", "test/corpus/comment_indent.c3", "test/corpus/comment_indent_f.c3", conf); +} diff --git a/test/corpus/comment_indent.c3 b/test/corpus/comment_indent.c3 new file mode 100644 index 0000000..8163ec3 --- /dev/null +++ b/test/corpus/comment_indent.c3 @@ -0,0 +1,27 @@ +enum MyEnum{ + A, // a comment + B, // another comment + C // last comment +} + +enum MyOtherEnum : (int val) { + A { 1 }, // a comment + B {2 }, // another comment + C {3} , // last comment +} + +enum MyThirdEnum : (int val) { + A { 1 }, // a comment + B {2 }, // another comment + C {3} // last comment +} + +fn void main() +{ + // if (foo) { + // bar(); + // } + + int a = 1; // comment 1 + int bbb = 2; // comment 2 +} diff --git a/test/corpus/comment_indent_f.c3 b/test/corpus/comment_indent_f.c3 new file mode 100644 index 0000000..1436622 --- /dev/null +++ b/test/corpus/comment_indent_f.c3 @@ -0,0 +1,30 @@ +enum MyEnum +{ + A, // a comment + B, // another comment + C // last comment +} + +enum MyOtherEnum : (int val) +{ + A { 1 }, // a comment + B { 2 }, // another comment + C { 3 }, // last comment +} + +enum MyThirdEnum : (int val) +{ + A { 1 }, // a comment + B { 2 }, // another comment + C { 3 } // last comment +} + +fn void main() +{ + // if (foo) { + // bar(); + // } + + int a = 1; // comment 1 + int bbb = 2; // comment 2 +} diff --git a/test/corpus/knr_braces.c3 b/test/corpus/knr_braces.c3 new file mode 100644 index 0000000..2e01779 --- /dev/null +++ b/test/corpus/knr_braces.c3 @@ -0,0 +1,22 @@ +struct MyStruct +{ + int x; +} + +fn void main() +{ + if (true) + { + return; + } + else + { + return; + } + + switch (1) + { + case 1: + break; + } +} diff --git a/test/corpus/knr_braces_f.c3 b/test/corpus/knr_braces_f.c3 new file mode 100644 index 0000000..48b1b6e --- /dev/null +++ b/test/corpus/knr_braces_f.c3 @@ -0,0 +1,16 @@ +struct MyStruct { + int x; +} + +fn void main() { + if (true) { + return; + } else { + return; + } + + switch (1) { + case 1: + break; + } +} diff --git a/test/corpus/struct_enum_bitstruct.c3 b/test/corpus/struct_enum_bitstruct.c3 index 4cbc62f..ccbadbe 100644 --- a/test/corpus/struct_enum_bitstruct.c3 +++ b/test/corpus/struct_enum_bitstruct.c3 @@ -29,40 +29,39 @@ struct } } -enum State -: int +enum State : int { WAITING, RUNNING, TERMINATED } enum State : int (String description) { - WAITING = "waiting", - RUNNING = "running", - TERMINATED = "ended" + WAITING { "waiting" }, + RUNNING { "running" }, + TERMINATED { "ended" } } enum State : int (String desc, bool active, Position pos) { - WAITING = { "waiting", false, { 1, 2} }, - RUNNING = { "running", true, {12,22} }, - TERMINATED = { "ended", false, { 0, 0} } + WAITING { "waiting", false, { 1, 2} }, + RUNNING { "running", true, {12,22} }, + TERMINATED { "ended", false, { 0, 0} } } -enum KeyCode : const CInt +enum KeyCode : Cint (Cint val) { - UNKNOWN=0,RETURN = 13, ESCAPE = 27, - BACKSPACE = 8, - TAB = 9, - SPACE = 32, + UNKNOWN {0},RETURN {13}, ESCAPE {27}, + BACKSPACE {8}, + TAB {9}, + SPACE {32}, EXCLAIM, QUOTEDBL, HASH, } -enum ConstInline : const inline String +enum ConstInline : String { - A = "Hello", - B = "World", + A { "Hello"}, + B { "World"}, } State current_state diff --git a/test/corpus/struct_enum_bitstruct_f.c3 b/test/corpus/struct_enum_bitstruct_f.c3 index 7ae842b..878c66f 100644 --- a/test/corpus/struct_enum_bitstruct_f.c3 +++ b/test/corpus/struct_enum_bitstruct_f.c3 @@ -40,35 +40,35 @@ enum State : int enum State : int (String description) { - WAITING = "waiting", - RUNNING = "running", - TERMINATED = "ended" + WAITING { "waiting" }, + RUNNING { "running" }, + TERMINATED { "ended" } } enum State : int (String desc, bool active, Position pos) { - WAITING = { "waiting", false, { 1, 2 } }, - RUNNING = { "running", true, { 12, 22 } }, - TERMINATED = { "ended", false, { 0, 0 } } + WAITING { "waiting", false, { 1, 2 } }, + RUNNING { "running", true, { 12, 22 } }, + TERMINATED { "ended", false, { 0, 0 } } } -enum KeyCode : const CInt +enum KeyCode : Cint (Cint val) { - UNKNOWN = 0, - RETURN = 13, - ESCAPE = 27, - BACKSPACE = 8, - TAB = 9, - SPACE = 32, + UNKNOWN { 0 }, + RETURN { 13 }, + ESCAPE { 27 }, + BACKSPACE { 8 }, + TAB { 9 }, + SPACE { 32 }, EXCLAIM, QUOTEDBL, HASH, } -enum ConstInline : const inline String +enum ConstInline : String { - A = "Hello", - B = "World", + A { "Hello" }, + B { "World" }, } State current_state = WAITING; \ No newline at end of file diff --git a/test/corpus/varargs.c3 b/test/corpus/varargs.c3 new file mode 100644 index 0000000..67f5e6b --- /dev/null +++ b/test/corpus/varargs.c3 @@ -0,0 +1,21 @@ +macro func1(args ...) +{ +} + +macro func1(args...) +{ +} + +fn void func2(int ...args) +{ +} + +fn void func3(int... args) +{ +} + +fn void spread_test() +{ + result = { ...result, .a = 1 }; + result = { ...result, ...other_thing }; +} diff --git a/test/corpus/varargs_f.c3 b/test/corpus/varargs_f.c3 new file mode 100644 index 0000000..e8b7374 --- /dev/null +++ b/test/corpus/varargs_f.c3 @@ -0,0 +1,21 @@ +macro func1(args...) +{ +} + +macro func1(args...) +{ +} + +fn void func2(int ...args) +{ +} + +fn void func3(int ...args) +{ +} + +fn void spread_test() +{ + result = { ...result, .a = 1 }; + result = { ...result, ...other_thing }; +} diff --git a/test/stability.c3 b/test/stability.c3 index 6d435bb..e2284eb 100644 --- a/test/stability.c3 +++ b/test/stability.c3 @@ -96,7 +96,7 @@ fn void stability() @test g_ctx.next_index.store(0); g_ctx.results = &results; - usz num_threads = os::num_cpu(); + int num_threads = os::num_cpu(); FixedThreadPool pool; pool.init(num_threads, num_threads)!!; defer pool.stop_and_destroy(); @@ -106,7 +106,7 @@ fn void stability() @test pool.push(&stability_worker_task)!!; } - pool.join()!!; + pool.join(); defer io::printfn("Stability test result: %d formatted, %d skipped due to syntax errors, %d format errors", results.formatted.load(), results.skipped.load(), results.errors.load()); diff --git a/test/stdlib.c3 b/test/stdlib.c3 index c2eba5e..f9c528b 100644 --- a/test/stdlib.c3 +++ b/test/stdlib.c3 @@ -114,7 +114,7 @@ fn void format_and_compile_std_lib() @test g_ctx.cwd_s = cwd_s; g_ctx.results = &results; - usz num_threads = os::num_cpu(); + int num_threads = os::num_cpu(); FixedThreadPool pool; pool.init(num_threads, num_threads)!!; defer pool.stop_and_destroy(); @@ -124,7 +124,7 @@ fn void format_and_compile_std_lib() @test pool.push(&stdlib_worker_task)!!; } - pool.join()!!; + pool.join(); io::printfn("Stdlib test result: %d formatted, %d skipped due to syntax errors.", results.formatted.load(), results.skipped.load()); } diff --git a/test/stdlib/src/_compiler_rt/atomic_rt.c3 b/test/stdlib/src/_compiler_rt/atomic_rt.c3 index cf6b3ab..23ac7ef 100644 --- a/test/stdlib/src/_compiler_rt/atomic_rt.c3 +++ b/test/stdlib/src/_compiler_rt/atomic_rt.c3 @@ -60,7 +60,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, int de = *(int*)desired; if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, (AtomicOrderingRT)success, (AtomicOrderingRT)failure, 4)) return 1; case 8: - $if iptr.sizeof >= 8: + $if iptr::size >= 8: long* pt = (long*)ptr; long ex = *(long*)expected; long de = *(long*)desired; diff --git a/test/stdlib/src/_compiler_rt/deprecated/atomic_nolibc_deprecated.c3 b/test/stdlib/src/_compiler_rt/deprecated/atomic_nolibc_deprecated.c3 deleted file mode 100644 index 0022d1d..0000000 --- a/test/stdlib/src/_compiler_rt/deprecated/atomic_nolibc_deprecated.c3 +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved. -// Use of this source code is governed by the MIT license -// a copy of which can be found in the LICENSE_STDLIB file. -module std::atomic; - -macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) -{ - switch (failure) - { - case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment); - case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment); - case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment); - default: unreachable("Unrecognized failure ordering"); - } - return 0; -} - -macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment) -{ - switch (success) - { - case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment); - case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment); - case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment); - case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment); - case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment); - default: unreachable("Unrecognized success ordering"); - } - return 0; -} - -fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @deprecated -{ - switch (size) - { - case 1: - char* pt = (char*)ptr; - char ex = *(char*)expected; - char de = *(char*)desired; - if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 1)) return 1; - case 2: - short* pt = (short*)ptr; - short ex = *(short*)expected; - short de = *(short*)desired; - if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 2)) return 1; - case 4: - int* pt = (int*)ptr; - int ex = *(int*)expected; - int de = *(int*)desired; - if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 4)) return 1; - case 8: - $if iptr.sizeof >= 8: - long* pt = (long*)ptr; - long ex = *(long*)expected; - long de = *(long*)desired; - if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 8)) return 1; - $else - nextcase; - $endif - default: - unreachable("Unsupported size (%d) for atomic_compare_exchange", size); - } - return 0; -} diff --git a/test/stdlib/src/_compiler_rt/deprecated/i128.c3 b/test/stdlib/src/_compiler_rt/deprecated/i128.c3 deleted file mode 100644 index f0fe03d..0000000 --- a/test/stdlib/src/_compiler_rt/deprecated/i128.c3 +++ /dev/null @@ -1,510 +0,0 @@ -module std::math::math_rt; - -fn int128 __divti3(int128 a, int128 b) @deprecated -{ - int128 sign_a = a >> 127; // -1 : 0 - int128 sign_b = b >> 127; // -1 : 0 - uint128 unsigned_a = (uint128)(a ^ sign_a) + (-sign_a); - uint128 unsigned_b = (uint128)(b ^ sign_b) + (-sign_b); - sign_a ^= sign_b; // quotient sign - return __udivti3(unsigned_a, unsigned_b) @inline ^ sign_a + (-sign_a); -} - -macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) @deprecated -{ - Int128bits n = { .all = a }; - Int128bits d = { .all = b }; - Int128bits q @noinit; - Int128bits r @noinit; - uint sr; - if (n.high == 0) - { - if (d.high == 0) - { - $if $return_rem: - return n.low % d.low; - $else - return n.low / d.low; - $endif - } - $if $return_rem: - return n.low; - $else - return 0; - $endif - } - if (d.low == 0) - { - if (d.high == 0) - { - $if $return_rem: - return n.high % d.low; - $else - return n.high / d.low; - $endif - } - if (n.low == 0) - { - $if $return_rem: - r.high = n.high % d.high; - r.low = 0; - return r.all; - $else - return n.high / d.high; - $endif - } - if (d.high & (d.high - 1) == 0) // d is pot - { - $if $return_rem: - r.low = n.low; - r.high = n.high & (d.high - 1); - return r.all; - $else - return (uint128)(n.high >> $$ctz(d.high)); - $endif - } - sr = (uint)$$clz(d.high) - (uint)$$clz(n.high); - // 0 <= sr <= n_udword_bits - 2 or sr large - if (sr > 64 - 2) - { - $if $return_rem: - return n.all; - $else - return 0; - $endif - } - sr++; - // 1 <= sr <= n_udword_bits - 1 - // q.all = n.all << (n_utword_bits - sr); - q.low = 0; - q.high = n.low << (64 - sr); - r.high = n.high >> sr; - r.low = (n.high << (64 - sr)) | (n.low >> sr); - } - else // d.s.low != 0 - { - if (d.high == 0) - { - if (d.low & (d.low - 1) == 0) // if d is a power of 2 - { - $if $return_rem: - return (uint128)(n.low & (d.low - 1)); - $else - if (d.low == 1) return n.all; - sr = (uint)$$ctz(d.low); - q.high = n.high >> sr; - q.low = (n.high << (64 - sr)) | (n.low >> sr); - return q.all; - $endif - } - sr = 1 + 64 + (uint)$$clz(d.low) - (uint)$$clz(n.high); - // 2 <= sr <= n_utword_bits - 1 - // q.all = n.all << (n_utword_bits - sr); - // r.all = n.all >> sr; - switch - { - case sr == 64: - q.low = 0; - q.high = n.low; - r.high = 0; - r.low = n.high; - case sr < 64: - q.low = 0; - q.high = n.low << (64 - sr); - r.high = n.high >> sr; - r.low = (n.high << (64 - sr)) | (n.low >> sr); - default: // n_udword_bits + 1 <= sr <= n_utword_bits - 1 - q.low = n.low << (128 - sr); - q.high = (n.high << (128 - sr)) | (n.low >> (sr - 64)); - r.high = 0; - r.low = n.high >> (sr - 64); - } - } - else - { - sr = (uint)$$clz(d.high) - (uint)$$clz(n.high); - // 0 <= sr <= n_udword_bits - 1 or sr large - if (sr > 64 - 1) - { - $if $return_rem: - return n.all; - $else - return 0; - $endif - } - - sr++; - // 1 <= sr <= n_udword_bits - // q.all = n.all << (n_utword_bits - sr); - // r.all = n.all >> sr; - q.low = 0; - if (sr == 64) - { - q.high = n.low; - r.high = 0; - r.low = n.high; - } - else - { - r.high = n.high >> sr; - r.low = (n.high << (64 - sr)) | (n.low >> sr); - q.high = n.low << (64 - sr); - } - } - } - // Not a special case - // q and r are initialized with: - // q.all = n.all << (128 - sr); - // r.all = n.all >> sr; - // 1 <= sr <= n_utword_bits - 1 - uint carry = 0; - for (; sr > 0; sr--) - { - // r:q = ((r:q) << 1) | carry - r.high = (r.high << 1) | (r.low >> (64 - 1)); - r.low = (r.low << 1) | (q.high >> (64 - 1)); - q.high = (q.high << 1) | (q.low >> (64 - 1)); - q.low = (q.low << 1) | carry; - // carry = 0; - // if (r.all >= d.all) - // { - // r.all -= d.all; - // carry = 1; - // } - int128 s = (int128)(d.all - r.all - 1) >> (128 - 1); - carry = (uint)(s & 1); - r.all -= d.all & s; - } - $if $return_rem: - return r.all; - $else - return (q.all << 1) | carry; - $endif -} - -fn uint128 __umodti3(uint128 n, uint128 d) @deprecated -{ - return @__udivmodti4(n, d, true); -} - -fn uint128 __udivti3(uint128 n, uint128 d) @deprecated -{ - return @__udivmodti4(n, d, false); -} - -fn int128 __modti3(int128 a, int128 b) @deprecated -{ - int128 sign = b >> 127; - uint128 unsigned_b = (uint128)(b ^ sign) + (-sign); - sign = a >> 127; - uint128 unsigned_a = (uint128)(a ^ sign) + (-sign); - - return __umodti3(unsigned_a, unsigned_b) ^ sign + (-sign); -} - -union Int128bits @private -{ - struct - { - ulong low; - ulong high; - } - uint128 all; -} - -fn uint128 __lshrti3(uint128 a, uint b) @deprecated -{ - Int128bits result; - result.all = a; - if (b >= 64) - { - result.low = result.high >> (b - 64); - result.high = 0; - } - else - { - if (b == 0) return a; - result.low = (result.high << (64 - b)) | (result.low >> b); - result.high = result.high >> b; - } - return result.all; -} - -fn int128 __ashrti3(int128 a, uint b) @deprecated -{ - Int128bits result; - result.all = a; - if (b >= 64) - { - result.low = result.high >> (b - 64); - result.high = result.high >> 63; - } - else - { - if (b == 0) return a; - result.low = result.high << (64 - b) | (result.low >> b); - result.high = result.high >> b; - } - return result.all; -} - -fn int128 __ashlti3(int128 a, uint b) @deprecated -{ - Int128bits result; - result.all = a; - if (b >= 64) - { - result.low = 0; - result.high = result.low << (b - 64); - } - else - { - if (b == 0) return a; - result.high = (result.high << b) | (result.low >> (64 - b)); - result.low = result.low << b; - } - return result.all; -} - -// Returns: a * b - -fn int128 __mulddi3(ulong a, ulong b) @private -{ - Int128bits r; - const ulong LOWER_MASK = 0xffff_ffff; - r.low = (a & LOWER_MASK) * (b & LOWER_MASK); - ulong t = r.low >> 32; - r.low &= LOWER_MASK; - t += (a >> 32) * (b & LOWER_MASK); - r.low += (t & LOWER_MASK) << 32; - r.high = t >> 32; - t = r.low >> 32; - r.low &= LOWER_MASK; - t += (b >> 32) * (a & LOWER_MASK); - r.low += (t & LOWER_MASK) << 32; - r.high += t >> 32; - r.high += (a >> 32) * (b >> 32); - return r.all; -} - -fn int128 __multi3(int128 a, int128 b) @deprecated -{ - Int128bits x = { .all = a }; - Int128bits y = { .all = b }; - Int128bits r = { .all = __mulddi3(x.low, y.low) }; - r.high += x.high * y.low + x.low * y.high; - return r.all; -} - -fn float __floattisf(int128 a) @deprecated => float_from_i128(float, a); -fn double __floattidf(int128 a) @deprecated => float_from_i128(double, a); -fn float __floatuntisf(uint128 a) @deprecated => float_from_u128(float, a); -fn double __floatuntidf(uint128 a) @deprecated => float_from_u128(double, a); -fn uint128 __fixunsdfti(double a) @deprecated => fixuint(a); -fn uint128 __fixunssfti(float a) @deprecated => fixuint(a); -fn int128 __fixdfti(double a) @deprecated => fixint(a); -fn int128 __fixsfti(float a) @deprecated => fixint(a); - - -macro float_from_i128($Type, a) @private -{ - var $Rep; - $switch $Type: - $case double: - $Rep = ulong; - const MANT_DIG = math::DOUBLE_MANT_DIG; - const SIGNIFICANT_BITS = 52; - const EXP_BIAS = 1023; - const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL; - const SIGN_BIT = 1UL << 63; - $case float: - $Rep = uint; - const MANT_DIG = math::FLOAT_MANT_DIG; - const EXP_BIAS = 127; - const SIGNIFICANT_BITS = 23; - const MANTISSA_MASK = 0x7F_FFFFU; - const SIGN_BIT = 1U << 31; - $case float16: - $Rep = ushort; - const MANT_DIG = math::HALF_MANT_DIG; - $case float128: - $Rep = uint128; - const MANT_DIG = QUAD_MANT_DIG; - $endswitch - if (a == 0) return ($Type)0; - // Grab and remove sign. - int128 sign = a >> 127; - a = (a ^ sign) - sign; - int sd = 128 - (int)$$clz(a); // digits - int e = sd - 1; // exponent - if (sd > MANT_DIG) - { - switch (sd) - { - case MANT_DIG + 1: - a <<= 1; - case MANT_DIG + 2: - break; - default: - a = (a >> (sd - (MANT_DIG + 2))) - | (uint128)((a & ((uint128)(-1) >> ((128 + MANT_DIG + 2) - sd))) != 0); - } - a |= (uint128)((a & 4) != 0); - a++; - a >>= 2; - if (a & (1LL << MANT_DIG)) - { - a >>= 1; - e++; - } - } - else - { - a <<= (MANT_DIG - sd); - } - return bitcast((($Rep)sign & SIGN_BIT) | ((($Rep)e + ($Rep)EXP_BIAS) << SIGNIFICANT_BITS) | (($Rep)a & ($Rep)MANTISSA_MASK), $Type); -} - -macro float_from_u128($Type, a) @private -{ - var $Rep; - $switch $Type: - $case double: - $Rep = ulong; - const MANT_DIG = math::DOUBLE_MANT_DIG; - const SIGNIFICANT_BITS = 52; - const EXP_BIAS = 1023; - const MANTISSA_MASK = 0xFFFFF_FFFF_FFFFUL; - $case float: - $Rep = uint; - const MANT_DIG = math::FLOAT_MANT_DIG; - const EXP_BIAS = 127; - const SIGNIFICANT_BITS = 23; - const MANTISSA_MASK = 0x7F_FFFFU; - $case float16: - $Rep = ushort; - const MANT_DIG = math::HALF_MANT_DIG; - $case float128: - $Rep = uint128; - const MANT_DIG = math::QUAD_MANT_DIG; - $endswitch - if (a == 0) return ($Type)0; - int sd = 128 - (int)$$clz(a); // digits - int e = sd - 1; // exponent - if (sd > MANT_DIG) - { - switch (sd) - { - case MANT_DIG + 1: - a <<= 1; - case MANT_DIG + 2: - break; - default: - a = (a >> (sd - (MANT_DIG + 2))) - | (uint128)((a & ((uint128)(-1) >> ((128 + MANT_DIG + 2) - sd))) != 0); - } - a |= (uint128)((a & 4) != 0); - a++; - a >>= 2; - if (a & (1LL << MANT_DIG)) - { - a >>= 1; - e++; - } - } - else - { - a <<= (MANT_DIG - sd); - } - return bitcast(((($Rep)e + ($Rep)EXP_BIAS) << SIGNIFICANT_BITS) | (($Rep)a & ($Rep)MANTISSA_MASK), $Type); -} - - -macro fixuint(a) @private -{ - var $Rep; - $switch $typeof(a): - $case double: - $Rep = ulong; - const EXPONENT_BITS = 11; - const SIGNIFICANT_BITS = 52; - $case float: - $Rep = uint; - const EXPONENT_BITS = 8; - const SIGNIFICANT_BITS = 23; - $case float16: - $Rep = ushort; - const EXPONENT_BITS = 5; - const SIGNIFICANT_BITS = 10; - $case float128: - $Rep = uint128; - const EXPONENT_BITS = 15; - const SIGNIFICANT_BITS = 112; - $endswitch - const $Rep MAX_EXPONENT = ($Rep)1 << EXPONENT_BITS - 1u; - const $Rep EXPONENT_BIAS = MAX_EXPONENT >> 1u; - const $Rep ONE_REP =EXPONENT_BIAS << SIGNIFICANT_BITS; - const $Rep SIGN_BIT = ($Rep)1 << (EXPONENT_BITS + SIGNIFICANT_BITS); - const $Rep ABS_MASK = SIGN_BIT - 1u; - const $Rep IMPLICIT_BIT = ($Rep)1 << SIGNIFICANT_BITS; - const $Rep SIGNIFICANT_MASK = IMPLICIT_BIT - 1u; - const $Rep EXPONENT_MASK = ABS_MASK ^ SIGNIFICANT_MASK; - const $Rep QUIET_BIT = IMPLICIT_BIT >> 1; - const $Rep QNAN_REP = EXPONENT_MASK | QUIET_BIT; - const $Rep INF_REP = EXPONENT_MASK; - - $Rep rep = bitcast(a, $Rep); - $Rep abs = rep & ABS_MASK; - int sign = rep & SIGN_BIT ? -1 : 1; - int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS); - $Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT; - if (sign == -1 || exponent < 0) return 0ULL; - if ((uint)exponent >= uint128.sizeof * 8) return ~0ULL; - if (exponent < SIGNIFICANT_BITS) return (uint128)significand >> (SIGNIFICANT_BITS - exponent); - return (uint128)significand << (exponent - SIGNIFICANT_BITS); -} - -macro fixint(a) @private -{ - var $Rep; - $switch $typeof(a): - $case double: - $Rep = ulong; - const EXPONENT_BITS = 11; - const SIGNIFICANT_BITS = 52; - $case float: - $Rep = uint; - const EXPONENT_BITS = 8; - const SIGNIFICANT_BITS = 23; - $case float16: - $Rep = ushort; - const EXPONENT_BITS = 5; - const SIGNIFICANT_BITS = 10; - $case float128: - $Rep = uint128; - const EXPONENT_BITS = 15; - const SIGNIFICANT_BITS = 112; - $endswitch - const $Rep MAX_EXPONENT = ($Rep)1 << EXPONENT_BITS - 1u; - const $Rep EXPONENT_BIAS = MAX_EXPONENT >> 1u; - const $Rep ONE_REP = EXPONENT_BIAS << SIGNIFICANT_BITS; - const $Rep SIGN_BIT = ($Rep)1 << (EXPONENT_BITS + SIGNIFICANT_BITS); - const $Rep ABS_MASK = SIGN_BIT - 1u; - const $Rep IMPLICIT_BIT = ($Rep)1 << SIGNIFICANT_BITS; - const $Rep SIGNIFICANT_MASK = IMPLICIT_BIT - 1u; - const $Rep EXPONENT_MASK = ABS_MASK ^ SIGNIFICANT_MASK; - const $Rep QUIET_BIT = IMPLICIT_BIT >> 1; - const $Rep QNAN_REP = EXPONENT_MASK | QUIET_BIT; - const $Rep INF_REP = EXPONENT_MASK; - - $Rep rep = bitcast(a, $Rep); - $Rep abs = rep & ABS_MASK; - int sign = rep & SIGN_BIT ? -1 : 1; - int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS); - $Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT; - if (exponent < 0) return 0; - if ((uint)exponent >= uint128.sizeof * 8) return sign == 1 ? int128.max : int128.min; - - if (exponent < SIGNIFICANT_BITS) return sign * ((int128)significand >> (SIGNIFICANT_BITS - exponent)); - return sign * ((int128)significand << (exponent - SIGNIFICANT_BITS)); -} diff --git a/test/stdlib/src/_compiler_rt/deprecated/math_supplemental.c3 b/test/stdlib/src/_compiler_rt/deprecated/math_supplemental.c3 deleted file mode 100644 index 1210177..0000000 --- a/test/stdlib/src/_compiler_rt/deprecated/math_supplemental.c3 +++ /dev/null @@ -1,90 +0,0 @@ -module std::math::math_rt; - -const double TOINT = 1 / math::DOUBLE_EPSILON; -const float TOINTF = (float)(1 / math::FLOAT_EPSILON); -macro force_eval_add(x, v) @deprecated -{ - $typeof(x) temp @noinit; - @volatile_store(temp, x + v); -} - -fn double __roundeven(double x) @deprecated -{ - ulong u = bitcast(x, ulong); - int e = (int)((u >> 52) & 0x7ff); - if (e >= 0x3ff + 52) return x; - bool signed = u >> 63 != 0; - if (signed) x = -x; - if (e < 0x3ff - 1) - { - /* raise inexact if x!=0 */ - force_eval_add(x, TOINT); - return 0 * x; - } - double y = (x + TOINT) - TOINT - x; - switch - { - case y > 0.5: - y = y + x - 1; - case y < -0.5: - y = y + x + 1; - case y == 0.5 || y == -0.5: - if (u & 1) - { - y = x + (y > 0 ? y + 1 : y - 1); - break; - } - nextcase; - default: - y = y + x; - } - if (signed) y = -y; - return y; -} - -fn float __roundevenf(float x) @deprecated -{ - uint u = bitcast(x, uint); - int e = (u >> 23) & 0xff; - if (e >= 0x7f + 23) return x; - bool signed = u >> 31 != 0; - if (signed) x = -x; - if (e < 0x7f - 1) - { - force_eval_add(x, TOINTF); - return 0 * x; - } - float y = (x + TOINTF) - TOINTF - x; - switch - { - case y > 0.5f: - y = y + x - 1; - case y < -0.5f: - y = y + x + 1; - case y == 0.5f || y == -0.5f: - if (u & 1) - { - y = x + (y > 0.0f ? y + 1.0f : y - 1.0f); - break; - } - nextcase; - default: - y = y + x; - } - if (signed) y = -y; - return y; -} - -fn double __powidf2(double a, int b) -{ - bool recip = b < 0; - double r = 1; - while (1) - { - if (b & 1) r *= a; - b /= 2; - if (b == 0) break; - a *= a; - } - return recip ? 1 / r : r; -} \ No newline at end of file diff --git a/test/stdlib/src/_compiler_rt/i128_rt.c3 b/test/stdlib/src/_compiler_rt/i128_rt.c3 index b24b547..19d6966 100644 --- a/test/stdlib/src/_compiler_rt/i128_rt.c3 +++ b/test/stdlib/src/_compiler_rt/i128_rt.c3 @@ -2,11 +2,12 @@ module compiler_rt; macro bitcast(expr, $Type) @local { - $if $Type.alignof <= $alignof(expr): + var $r = $reflect(expr); + $if $Type::alignment <= $r.alignment: return *($Type*)&expr; $else $Type x @noinit; - $$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr)); + $$memcpy(&x, &expr, @sizeof(expr), false, $Type::alignment, $r.alignment); return x; $endif } @@ -14,10 +15,10 @@ fn int128 __divti3(int128 a, int128 b) @cname("__divti3") @weak @nostrip { int128 sign_a = a >> 127; // -1 : 0 int128 sign_b = b >> 127; // -1 : 0 - uint128 unsigned_a = (uint128)(a ^ sign_a) + (-sign_a); - uint128 unsigned_b = (uint128)(b ^ sign_b) + (-sign_b); + uint128 unsigned_a = (uint128)((a ^ sign_a) - sign_a); + uint128 unsigned_b = (uint128)((b ^ sign_b) - sign_b); sign_a ^= sign_b; // quotient sign - return __udivti3(unsigned_a, unsigned_b) @inline ^ sign_a + (-sign_a); + return (int128)__udivti3(unsigned_a, unsigned_b) @inline ^ sign_a + (-sign_a); } macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) @@ -26,7 +27,7 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) Int128bits d = { .all = b }; Int128bits q @noinit; Int128bits r @noinit; - uint sr; + int sr; if (n.high == 0) { if (d.high == 0) @@ -43,9 +44,9 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) return 0; $endif } - if (d.low == 0) + if (!d.low) { - if (d.high == 0) + if (!d.high) { $if $return_rem: return n.high % d.low; @@ -53,7 +54,7 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) return n.high / d.low; $endif } - if (n.low == 0) + if (!n.low) { $if $return_rem: r.high = n.high % d.high; @@ -73,7 +74,7 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) return (uint128)(n.high >> $$ctz(d.high)); $endif } - sr = (uint)$$clz(d.high) - (uint)$$clz(n.high); + sr = (int)$$clz(d.high) - (int)$$clz(n.high); // 0 <= sr <= n_udword_bits - 2 or sr large if (sr > 64 - 2) { @@ -101,13 +102,13 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) return (uint128)(n.low & (d.low - 1)); $else if (d.low == 1) return n.all; - sr = (uint)$$ctz(d.low); + sr = (int)$$ctz(d.low); q.high = n.high >> sr; q.low = (n.high << (64 - sr)) | (n.low >> sr); return q.all; $endif } - sr = 1 + 64 + (uint)$$clz(d.low) - (uint)$$clz(n.high); + sr = 1 + 64 + (int)$$clz(d.low) - (int)$$clz(n.high); // 2 <= sr <= n_utword_bits - 1 // q.all = n.all << (n_utword_bits - sr); // r.all = n.all >> sr; @@ -132,7 +133,7 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) } else { - sr = (uint)$$clz(d.high) - (uint)$$clz(n.high); + sr = (int)$$clz(d.high) - (int)$$clz(n.high); // 0 <= sr <= n_udword_bits - 1 or sr large if (sr > 64 - 1) { @@ -181,7 +182,7 @@ macro uint128 @__udivmodti4(uint128 a, uint128 b, bool $return_rem) // r.all -= d.all; // carry = 1; // } - int128 s = (int128)(d.all - r.all - 1) >> (128 - 1); + uint128 s = (uint128)((int128)(d.all - r.all - 1) >> (128 - 1)); carry = (uint)(s & 1); r.all -= d.all & s; } @@ -205,11 +206,11 @@ fn uint128 __udivti3(uint128 n, uint128 d) @cname("__udivti3") @weak @nostrip fn int128 __modti3(int128 a, int128 b) @cname("__modti3") @weak @nostrip { int128 sign = b >> 127; - uint128 unsigned_b = (uint128)(b ^ sign) + (-sign); + uint128 unsigned_b = (uint128)((b ^ sign) + (-sign)); sign = a >> 127; - uint128 unsigned_a = (uint128)(a ^ sign) + (-sign); + uint128 unsigned_a = (uint128)((a ^ sign) + (-sign)); - return __umodti3(unsigned_a, unsigned_b) ^ sign + (-sign); + return (int128)__umodti3(unsigned_a, unsigned_b) ^ sign + (-sign); } union Int128bits @private @@ -243,7 +244,7 @@ fn uint128 __lshrti3(uint128 a, uint b) @cname("__lshrti3") @weak @nostrip fn int128 __ashrti3(int128 a, uint b) @cname("__ashrti3") @weak @nostrip { Int128bits result; - result.all = a; + result.all = (uint128)a; if (b >= 64) { result.low = result.high >> (b - 64); @@ -255,13 +256,13 @@ fn int128 __ashrti3(int128 a, uint b) @cname("__ashrti3") @weak @nostrip result.low = result.high << (64 - b) | (result.low >> b); result.high = result.high >> b; } - return result.all; + return (int128)result.all; } fn int128 __ashlti3(int128 a, uint b) @cname("__ashlti3") @weak @nostrip { Int128bits result; - result.all = a; + result.all = (uint128)a; if (b >= 64) { result.low = 0; @@ -273,7 +274,7 @@ fn int128 __ashlti3(int128 a, uint b) @cname("__ashlti3") @weak @nostrip result.high = (result.high << b) | (result.low >> (64 - b)); result.low = result.low << b; } - return result.all; + return (int128)result.all; } // Returns: a * b @@ -294,16 +295,16 @@ fn int128 __mulddi3(ulong a, ulong b) @private r.low += (t & LOWER_MASK) << 32; r.high += t >> 32; r.high += (a >> 32) * (b >> 32); - return r.all; + return (int128)r.all; } fn int128 __multi3(int128 a, int128 b) @cname("__multi3") @weak @nostrip { - Int128bits x = { .all = a }; - Int128bits y = { .all = b }; - Int128bits r = { .all = __mulddi3(x.low, y.low) }; + Int128bits x = { .all = (uint128)a }; + Int128bits y = { .all = (uint128)b }; + Int128bits r = { .all = (uint128)__mulddi3(x.low, y.low) }; r.high += x.high * y.low + x.low * y.high; - return r.all; + return (int128)r.all; } fn float __floattisf(int128 a) @cname("__floattisf") @weak @nostrip => float_from_i128(float, a); @@ -316,7 +317,7 @@ fn int128 __fixdfti(double a) @weak @cname("__fixdfti") @nostrip => fixint(a); fn int128 __fixsfti(float a) @weak @cname("__fixsfti") @nostrip => fixint(a); -macro float_from_i128($Type, a) @private +macro float_from_i128($Type, int128 a) @private { var $Rep; $switch $Type: @@ -341,7 +342,7 @@ macro float_from_i128($Type, a) @private $Rep = uint128; const MANT_DIG = QUAD_MANT_DIG; $endswitch - if (a == 0) return ($Type)0; + if (a == 0) return 0; // Grab and remove sign. int128 sign = a >> 127; a = (a ^ sign) - sign; @@ -357,9 +358,9 @@ macro float_from_i128($Type, a) @private break; default: a = (a >> (sd - (MANT_DIG + 2))) - | (uint128)((a & ((uint128)(-1) >> ((128 + MANT_DIG + 2) - sd))) != 0); + | (int128)(((uint128)a & ((uint128)(-1) >> ((128 + MANT_DIG + 2) - sd))) != 0); } - a |= (uint128)((a & 4) != 0); + a |= (int128)((a & 4) != 0); a++; a >>= 2; if (a & (1LL << MANT_DIG)) @@ -375,7 +376,7 @@ macro float_from_i128($Type, a) @private return bitcast((($Rep)sign & SIGN_BIT) | ((($Rep)e + ($Rep)EXP_BIAS) << SIGNIFICANT_BITS) | (($Rep)a & ($Rep)MANTISSA_MASK), $Type); } -macro float_from_u128($Type, a) @private +macro float_from_u128($Type, uint128 a) @private { var $Rep; $switch $Type: @@ -412,7 +413,7 @@ macro float_from_u128($Type, a) @private a |= (uint128)((a & 4) != 0); a++; a >>= 2; - if (a & (1LL << MANT_DIG)) + if (a & (1ULL << MANT_DIG)) { a >>= 1; e++; @@ -465,7 +466,7 @@ macro fixuint(a) @private int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS); $Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT; if (sign == -1 || exponent < 0) return 0ULL; - if ((uint)exponent >= uint128.sizeof * 8) return ~0ULL; + if ((uint)exponent >= uint128::size * 8) return ~0ULL; if (exponent < SIGNIFICANT_BITS) return (uint128)significand >> (SIGNIFICANT_BITS - exponent); return (uint128)significand << (exponent - SIGNIFICANT_BITS); } @@ -509,7 +510,7 @@ macro fixint(a) @private int exponent = (int)((abs >> SIGNIFICANT_BITS) - EXPONENT_BIAS); $Rep significand = (abs & SIGNIFICANT_MASK) | IMPLICIT_BIT; if (exponent < 0) return 0; - if ((uint)exponent >= uint128.sizeof * 8) return sign == 1 ? int128.max : int128.min; + if ((uint)exponent >= uint128::size * 8) return sign == 1 ? int128::max : int128::min; if (exponent < SIGNIFICANT_BITS) return sign * ((int128)significand >> (SIGNIFICANT_BITS - exponent)); return sign * ((int128)significand << (exponent - SIGNIFICANT_BITS)); diff --git a/test/stdlib/src/_compiler_rt/math_rt.c3 b/test/stdlib/src/_compiler_rt/math_rt.c3 index d2b1f9e..1fc36c8 100644 --- a/test/stdlib/src/_compiler_rt/math_rt.c3 +++ b/test/stdlib/src/_compiler_rt/math_rt.c3 @@ -49,7 +49,7 @@ fn double __roundeven(double x) @cname("roundeven") @weak @nostrip fn float __roundevenf(float x) @cname("roundevenf") @weak @nostrip { uint u = bitcast(x, uint); - int e = (u >> 23) & 0xff; + int e = (int)((u >> 23) & 0xff); if (e >= 0x7f + 23) return x; bool signed = u >> 31 != 0; if (signed) x = -x; diff --git a/test/stdlib/src/_nolibc/deprecated/nolibc_deprecated.c3 b/test/stdlib/src/_nolibc/deprecated/nolibc_deprecated.c3 deleted file mode 100644 index c7397d0..0000000 --- a/test/stdlib/src/_nolibc/deprecated/nolibc_deprecated.c3 +++ /dev/null @@ -1,6 +0,0 @@ -module std::math::nolibc @if(env::NO_LIBC); - -fn void __stack_chk_fail() @deprecated -{ - $$trap(); -} \ No newline at end of file diff --git a/test/stdlib/src/_nolibc/math_nolibc/__tan.c3 b/test/stdlib/src/_nolibc/math_nolibc/__tan.c3 index 57f5bfc..b84f733 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/__tan.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/__tan.c3 @@ -37,7 +37,7 @@ fn double __tan(double x, double y, int odd) @cname("__tan") @weak @nostrip int sign @noinit; if (big) { - sign = hx >> 31; + sign = (int)(hx >> 31); if (sign) { x = -x; diff --git a/test/stdlib/src/_nolibc/math_nolibc/acos.c3 b/test/stdlib/src/_nolibc/math_nolibc/acos.c3 index d66563a..8ec29bc 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/acos.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/acos.c3 @@ -47,7 +47,7 @@ fn double _acos(double x) @weak @cname("acos") @nostrip if (hx >> 31) return 2. * PIO2_HI + 0x1p-120f; return 0.; } - return double.nan; + return double::nan; /* |x| < 0.5 */ case ix < 0x3fe00000: /* |x| < 2**-57 */ @@ -113,7 +113,7 @@ fn float _acosf(float x) @weak @cname("acosf") @nostrip if (hx >> 31) return 2.f * PIO2_HI_F + 0x1p-120f; return 0; } - return float.nan; + return float::nan; /* |x| < 0.5 */ case ix < 0x3f000000: /* |x| < 2**-26 */ diff --git a/test/stdlib/src/_nolibc/math_nolibc/asin.c3 b/test/stdlib/src/_nolibc/math_nolibc/asin.c3 index 0537bf7..56b343f 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/asin.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/asin.c3 @@ -46,7 +46,7 @@ fn double _asin(double x) @weak @cname("asin") @nostrip /* asin(1) = +-pi/2 with inexact */ return x * PIO2_HI + 0x1p-120f; } - return double.nan; + return double::nan; /* |x| < 0.5 */ case ix < 0x3fe00000: /* if 0x1p-1022 <= |x| < 0x1p-26, avoid raising underflow */ @@ -115,7 +115,7 @@ fn float _asinf(float x) @weak @cname("asinf") @nostrip /* asin(+-1) = +-pi/2 with inexact */ return x * (float)PIO2 + 0x1p-120f; } - return float.nan; + return float::nan; /* |x| < 0.5 */ case ix < 0x3f000000: /* if 0x1p-126 <= |x| < 0x1p-12, avoid raising underflow */ diff --git a/test/stdlib/src/_nolibc/math_nolibc/atan.c3 b/test/stdlib/src/_nolibc/math_nolibc/atan.c3 index 6fc19e9..c4065f7 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/atan.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/atan.c3 @@ -245,7 +245,7 @@ fn double _atan2(double y, double x) @weak @cname("atan2") @nostrip } } // when x = 0 - if (ix | lx == 0) return m & 1 ? -math::PI_2 : math::PI_2; + if (ix | lx == 0) return m & 1 ? -math::HALF_PI : math::HALF_PI; /* when x is INF */ if (ix == 0x7ff00000) { @@ -253,10 +253,10 @@ fn double _atan2(double y, double x) @weak @cname("atan2") @nostrip { switch (m) { - case 0: return math::PI_4; /* atan(+INF,+INF) */ - case 1: return -math::PI_4; /* atan(-INF,+INF) */ - case 2: return math::PI_4 + math::PI_2; /* atan(+INF,-INF) */ - case 3: return - (math::PI_4 + math::PI_2); /* atan(-INF,-INF) */ + case 0: return math::QUARTER_PI; /* atan(+INF,+INF) */ + case 1: return -math::QUARTER_PI; /* atan(-INF,+INF) */ + case 2: return math::QUARTER_PI + math::HALF_PI; /* atan(+INF,-INF) */ + case 3: return - (math::QUARTER_PI + math::HALF_PI); /* atan(-INF,-INF) */ } unreachable(); } @@ -270,7 +270,7 @@ fn double _atan2(double y, double x) @weak @cname("atan2") @nostrip unreachable(); } /* |y/x| > 0x1p64 */ - if (ix + (64 << 20) < iy || iy == 0x7ff00000) return m & 1 ? -math::PI_2 : math::PI_2; + if (ix + (64 << 20) < iy || iy == 0x7ff00000) return m & 1 ? -math::HALF_PI : math::HALF_PI; /* z = atan(|y/x|) without spurious underflow */ double z = ((m & 2) && iy + (64 << 20) < ix) ? 0 : _atan(math::abs(y/x)); @@ -326,7 +326,7 @@ fn float _atan2f(float y, float x) @weak @cname("atan2f") @nostrip unreachable(); } /* when x = 0 */ - if (ix == 0) return m & 1 ? -(float)math::PI_2 : (float)math::PI_2; + if (ix == 0) return m & 1 ? -(float)math::HALF_PI : (float)math::HALF_PI; /* when x is INF */ if (ix == 0x7f800000) { @@ -334,10 +334,10 @@ fn float _atan2f(float y, float x) @weak @cname("atan2f") @nostrip { switch (m) { - case 0: return (float)math::PI_4; /* atan(+INF,+INF) */ - case 1: return (float)-math::PI_4; /* atan(-INF,+INF) */ - case 2: return (float)(math::PI_4 + math::PI_2); /*atan(+INF,-INF)*/ - case 3: return -(float)(math::PI_4 + math::PI_2); /*atan(-INF,-INF)*/ + case 0: return (float)math::QUARTER_PI; /* atan(+INF,+INF) */ + case 1: return (float)-math::QUARTER_PI; /* atan(-INF,+INF) */ + case 2: return (float)(math::QUARTER_PI + math::HALF_PI); /*atan(+INF,-INF)*/ + case 3: return -(float)(math::QUARTER_PI + math::HALF_PI); /*atan(-INF,-INF)*/ } unreachable(); } @@ -351,7 +351,7 @@ fn float _atan2f(float y, float x) @weak @cname("atan2f") @nostrip unreachable(); } /* |y/x| > 0x1p26 */ - if (ix + 26 << 23 < iy || iy == 0x7f800000) return m & 1 ? -(float)math::PI_2 : (float)math::PI_2; + if (ix + 26 << 23 < iy || iy == 0x7f800000) return m & 1 ? -(float)math::HALF_PI : (float)math::HALF_PI; /* z = atan(|y/x|) with correct underflow */ /*|y/x| < 0x1p-26, x < 0 */ diff --git a/test/stdlib/src/_nolibc/math_nolibc/atanh.c3 b/test/stdlib/src/_nolibc/math_nolibc/atanh.c3 index 24c04f8..f4b30e6 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/atanh.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/atanh.c3 @@ -25,9 +25,9 @@ fn double _atanh(double x) @weak @cname("atanh") @nostrip uint lx = x.low_word(); if ((ix - 0x3ff00000 | lx) == 0) { - return sign ? -double.inf : double.inf; + return sign ? -double::inf : double::inf; } - return double.nan; + return double::nan; /* x<2**-28 */ case ix < 0x3e300000 && (1e300 + x) > 0.0: return x; @@ -73,9 +73,9 @@ fn float _atanhf(float x) @weak @cname("atanhf") @nostrip case ix >= 0x3f800000: if (ix == 0x3f800000) { - return sign ? -float.inf : float.inf; + return sign ? -float::inf : float::inf; } - return float.nan; + return float::nan; /* x<2**-28 */ case ix < 0x31800000 && (1e30 + x) > 0.f: return x; diff --git a/test/stdlib/src/_nolibc/math_nolibc/erf.c3 b/test/stdlib/src/_nolibc/math_nolibc/erf.c3 index 649924f..230e570 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/erf.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/erf.c3 @@ -7,8 +7,8 @@ fn float _erff(float x) { // Handle special cases. if (x == 0.0) return 0.0; - if (x == float.inf) return 1.0; - if (x == -float.inf) return -1.0; + if (x == float::inf) return 1.0; + if (x == -float::inf) return -1.0; // Use symmetry: erf(-x) = -erf(x) float sign = 1.0; @@ -44,8 +44,8 @@ fn double _erf(double x) { // Handle special cases. if (x == 0.0) return 0.0; - if (x == double.inf) return 1.0; - if (x == -double.inf) return -1.0; + if (x == double::inf) return 1.0; + if (x == -double::inf) return -1.0; // Use symmetry: erf(-x) = -erf(x) double sign = 1.0; diff --git a/test/stdlib/src/_nolibc/math_nolibc/exp.c3 b/test/stdlib/src/_nolibc/math_nolibc/exp.c3 index c3443d5..88c4769 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/exp.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/exp.c3 @@ -20,10 +20,10 @@ const float EXPF_P4 = -1.6533901999e-06f; fn double exp(double x) @cname("exp") @nostrip @weak { if (x != x) return x; - if (x == double.inf) return double.inf; - if (x == -double.inf) return 0.0; // IEEE 754 spec + if (x == double::inf) return double::inf; + if (x == -double::inf) return 0.0; // IEEE 754 spec // Overflow threshold for exp (approx +709.78 for double) - if (x > 709.782712893384) return double.inf; + if (x > 709.782712893384) return double::inf; // Underflow threshold for exp (approx -745.13 for double) if (x < -745.133219101941) return 0.0; @@ -41,10 +41,10 @@ fn double exp(double x) @cname("exp") @nostrip @weak fn float expf(float x) @cname("expf") @nostrip @weak { if (x != x) return x; - if (x == float.inf) return float.inf; - if (x == -float.inf) return 0.0f; // IEEE 754 spec + if (x == float::inf) return float::inf; + if (x == -float::inf) return 0.0f; // IEEE 754 spec // Overflow threshold (approx +88.72 for float) - if (x > 88.7228f) return float.inf; + if (x > 88.7228f) return float::inf; // Underflow threshold (approx -103.97 for float) if (x < -103.972084f) return 0.0f; diff --git a/test/stdlib/src/_nolibc/math_nolibc/exp2.c3 b/test/stdlib/src/_nolibc/math_nolibc/exp2.c3 index 5180df7..a9fc98d 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/exp2.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/exp2.c3 @@ -12,9 +12,9 @@ fn float _exp2f(float x) @cname("exp2f") @weak @nostrip switch { // |x| >= 128 or x is nan. - case bitcast(x, uint) == bitcast(-float.inf, uint): + case bitcast(x, uint) == bitcast(-float::inf, uint): return 0; - case abstop >= _top12f(float.inf): + case abstop >= _top12f(float::inf): return x + x; case x > 0: return __math_oflowf(0); @@ -96,9 +96,9 @@ fn double _exp2(double x) @cname("exp2") @weak @nostrip { switch { - case u == bitcast(-double.inf, ulong): + case u == bitcast(-double::inf, ulong): return 0.0; - case abstop >= _top12d(double.inf): + case abstop >= _top12d(double::inf): return 1.0 + x; case !(u >> 63): return __math_oflow(0); diff --git a/test/stdlib/src/_nolibc/math_nolibc/gamma.c3 b/test/stdlib/src/_nolibc/math_nolibc/gamma.c3 index b8f6270..22b70bc 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/gamma.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/gamma.c3 @@ -51,12 +51,12 @@ fn double lgamma(double x) fn double tgamma(double x) { // Handle special cases. - if (x == 0.0) return double.inf; + if (x == 0.0) return double::inf; // Check for negative integers (poles). if (x < 0.0 && x == math::floor(x)) { - return double.nan; + return double::nan; } // For positive values, use exp(lgamma(x)). diff --git a/test/stdlib/src/_nolibc/math_nolibc/ldexp.c3 b/test/stdlib/src/_nolibc/math_nolibc/ldexp.c3 index f852104..25d74c0 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/ldexp.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/ldexp.c3 @@ -18,7 +18,7 @@ fn double ldexp(double x, int exp) @cname("ldexp") // new exponent calculation hexp += exp; - if (hexp > 0x7fe) return x * double.inf; + if (hexp > 0x7fe) return x * double::inf; if (hexp < 1) { x *= 0x1p-1022; @@ -52,7 +52,7 @@ fn float ldexpf(float x, int exp) @cname("ldexpf") // new exponent calculation hexp += exp; - if (hexp > 0xfe) return x * float.inf; + if (hexp > 0xfe) return x * float::inf; if (hexp < 1) { x *= 0x1p-126f; @@ -62,6 +62,6 @@ fn float ldexpf(float x, int exp) @cname("ldexpf") } // set new exponent - ix = (ix & 0x807fffff) | (hexp << 23); + ix = (ix & 0x807fffff) | (uint)(hexp << 23); return bitcast(ix, float); } diff --git a/test/stdlib/src/_nolibc/math_nolibc/log.c3 b/test/stdlib/src/_nolibc/math_nolibc/log.c3 index 54352c3..ff2abd5 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/log.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/log.c3 @@ -22,9 +22,9 @@ const float SQRT2F = 1.41421356237309504880f; fn double log(double x) @cname("log") @nostrip @weak { if (x != x) return x; - if (x < 0.0) return double.nan; - if (x == 0.0) return -double.inf; - if (x == double.inf) return double.inf; + if (x < 0.0) return double::nan; + if (x == 0.0) return -double::inf; + if (x == double::inf) return double::inf; int k; double f = frexp(x, &k); @@ -53,9 +53,9 @@ fn double log(double x) @cname("log") @nostrip @weak fn float logf(float x) @cname("logf") @nostrip @weak { if (x != x) return x; - if (x < 0.0f) return float.nan; - if (x == 0.0f) return -float.inf; - if (x == float.inf) return float.inf; + if (x < 0.0f) return float::nan; + if (x == 0.0f) return -float::inf; + if (x == float::inf) return float::inf; int k; float f = frexpf(x, &k); diff --git a/test/stdlib/src/_nolibc/math_nolibc/log1p.c3 b/test/stdlib/src/_nolibc/math_nolibc/log1p.c3 index e7a8b1a..01c32cd 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/log1p.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/log1p.c3 @@ -62,8 +62,8 @@ fn double _log1p(double x) @weak @cname("log1p") @nostrip { /* x <= -1.0 */ case hx >= 0xbff00000: - if (x == -1) return -double.inf; - return double.nan; + if (x == -1) return -double::inf; + return double::nan; /* |x| < 2**-53 */ case hx << 1 < 0x3ca00000 << 1: /* underflow if subnormal */ @@ -176,8 +176,8 @@ fn float _log1pf(float x) @weak @cname("log1pf") @nostrip { /* x <= -1.0 */ case ix >= 0xbf800000: - if (x == -1) return -float.inf; - return float.nan; + if (x == -1) return -float::inf; + return float::nan; /* |x| < 2**-24 */ case ix << 1 < 0x33800000 << 1: /* underflow if subnormal */ diff --git a/test/stdlib/src/_nolibc/math_nolibc/math_nolibc.c3 b/test/stdlib/src/_nolibc/math_nolibc/math_nolibc.c3 index 5e32b41..396c1c6 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/math_nolibc.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/math_nolibc.c3 @@ -3,9 +3,9 @@ module std::math::nolibc @if(env::NO_LIBC || $feature(C3_MATH)); const double TOINT = 1 / math::DOUBLE_EPSILON; const double TOINT15 = 1.5 / math::DOUBLE_EPSILON; const float TOINTF = (float)(1 / math::FLOAT_EPSILON); -const double S1PI2 @private = math::PI_2; /* 0x3FF921FB, 0x54442D18 */ +const double S1PI2 @private = math::HALF_PI; /* 0x3FF921FB, 0x54442D18 */ const double S2PI2 @private = math::PI; /* 0x400921FB, 0x54442D18 */ -const double S3PI2 @private = math::PI + math::PI_2; /* 0x4012D97C, 0x7F3321D2 */ +const double S3PI2 @private = math::PI + math::HALF_PI; /* 0x4012D97C, 0x7F3321D2 */ const double S4PI2 @private = math::PI + math::PI; /* 0x401921FB, 0x54442D18 */ // Shared between expf, exp2f and powf. diff --git a/test/stdlib/src/_nolibc/math_nolibc/pow.c3 b/test/stdlib/src/_nolibc/math_nolibc/pow.c3 index 88eee9e..a8c3cfc 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/pow.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/pow.c3 @@ -2,34 +2,34 @@ module std::math::nolibc @if(env::NO_LIBC || $feature(C3_MATH)); fn double pow(double x, double y) @cname("pow") { - if (x != x || y != y) return double.nan; + if (x != x || y != y) return double::nan; - if (y == double.inf) + if (y == double::inf) { if (x == 1.0 || x == -1.0) return 1.0; - return (_fabs(x) < 1.0) ? 0.0 : double.inf; + return (_fabs(x) < 1.0) ? 0.0 : double::inf; } - if (y == -double.inf) + if (y == -double::inf) { if (x == 1.0 || x == -1.0) return 1.0; - return (_fabs(x) < 1.0) ? double.inf : 0.0; + return (_fabs(x) < 1.0) ? double::inf : 0.0; } - if (x == double.inf) + if (x == double::inf) { - return (y < 0.0) ? 0.0 : double.inf; + return (y < 0.0) ? 0.0 : double::inf; } - if (x == -double.inf) + if (x == -double::inf) { - if (y != _floor(y)) return -double.nan; + if (y != _floor(y)) return -double::nan; if (y < 0.0) return 0.0; - return ((int)y & 1) ? -double.inf : double.inf; + return ((int)y & 1) ? -double::inf : double::inf; } if (y == 0.0) return 1.0; if (x == 0.0) { - if (y < 0.0) return double.inf; + if (y < 0.0) return double::inf; if (y > 0.0) return 0.0; return 1.0; } @@ -39,15 +39,15 @@ fn double pow(double x, double y) @cname("pow") if (x < 0.0) { - if (y != _floor(y)) return double.nan; + if (y != _floor(y)) return double::nan; return ((int)y & 1) ? -pow(-x, y) : pow(-x, y); } double result = exp(y * log(x)); - if (result == double.inf || result == -double.inf) + if (result == double::inf || result == -double::inf) { - return (y < 0.0) ? 0.0 : double.inf; + return (y < 0.0) ? 0.0 : double::inf; } if (result == 0.0) return 0.0; @@ -56,34 +56,34 @@ fn double pow(double x, double y) @cname("pow") fn float powf(float x, float y) @cname("powf") { - if (x != x || y != y) return float.nan; + if (x != x || y != y) return float::nan; - if (y == float.inf) + if (y == float::inf) { if (x == 1.0f || x == -1.0f) return 1.0f; - return (_fabsf(x) < 1.0f) ? 0.0f : float.inf; + return (_fabsf(x) < 1.0f) ? 0.0f : float::inf; } - if (y == -float.inf) + if (y == -float::inf) { if (x == 1.0f || x == -1.0f) return 1.0f; - return (_fabsf(x) < 1.0f) ? float.inf : 0.0f; + return (_fabsf(x) < 1.0f) ? float::inf : 0.0f; } - if (x == float.inf) + if (x == float::inf) { - return (y < 0.0f) ? 0.0f : float.inf; + return (y < 0.0f) ? 0.0f : float::inf; } - if (x == -float.inf) + if (x == -float::inf) { - if (y != _floorf(y)) return float.nan; + if (y != _floorf(y)) return float::nan; if (y < 0.0f) return 0.0f; - return ((int)y & 1) ? -float.inf : float.inf; + return ((int)y & 1) ? -float::inf : float::inf; } if (y == 0.0f) return 1.0f; if (x == 0.0f) { - if (y < 0.0f) return float.inf; + if (y < 0.0f) return float::inf; if (y > 0.0f) return 0.0f; return 1.0f; } @@ -93,15 +93,15 @@ fn float powf(float x, float y) @cname("powf") if (x < 0.0f) { - if (y != _floorf(y)) return float.nan; + if (y != _floorf(y)) return float::nan; return ((int)y & 1) ? -powf(-x, y) : powf(-x, y); } float result = expf(y * logf(x)); - if (result == float.inf || result == -float.inf) + if (result == float::inf || result == -float::inf) { - return (y < 0.0f) ? 0.0f : float.inf; + return (y < 0.0f) ? 0.0f : float::inf; } if (result == 0.0f) return 0.0f; diff --git a/test/stdlib/src/_nolibc/math_nolibc/rempi.c3 b/test/stdlib/src/_nolibc/math_nolibc/rempi.c3 index c7dcd26..7b5cda6 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/rempi.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/rempi.c3 @@ -67,9 +67,9 @@ fn int __rem_pio2f(float x, double *y) return 0; } /* scale x into [2^23, 2^24-1] */ - int sign = ux >> 31; - int e0 = (ix >> 23) - (0x7f + 23); /* e0 = ilogb(|x|)-23, positive */ - ux = ix - e0 << 23; + int sign = (int)(ux >> 31); + int e0 = (int)(ix >> 23) - (0x7f + 23); /* e0 = ilogb(|x|)-23, positive */ + ux = (uint)((int)ix - e0 << 23); double tx = bitcast(ux, float); double ty @noinit; int n = __rem_pio2_large(&tx,&ty, e0, 1, 0); diff --git a/test/stdlib/src/_nolibc/math_nolibc/round.c3 b/test/stdlib/src/_nolibc/math_nolibc/round.c3 index 0189479..7fbc283 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/round.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/round.c3 @@ -29,7 +29,7 @@ fn double _round(double x) @cname("round") @weak @nostrip fn float _roundf(float x) @cname("roundf") @weak @nostrip { uint u = bitcast(x, uint); - int e = (u >> 23) & 0xff; + int e = (int)(u >> 23) & 0xff; if (e >= 0x7f + 23) return x; bool signed = u >> 31 != 0; if (signed) x = -x; diff --git a/test/stdlib/src/_nolibc/math_nolibc/sin.c3 b/test/stdlib/src/_nolibc/math_nolibc/sin.c3 index 8e7dbe1..60e02bd 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/sin.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/sin.c3 @@ -19,7 +19,7 @@ module std::math::nolibc @if(env::NO_LIBC || $feature(C3_MATH)); fn float _sinf(float x) @weak @cname("sinf") @nostrip { uint ix = x.word(); - int sign = ix >> 31; + int sign = (int)(ix >> 31); ix &= 0x7fffffff; switch { diff --git a/test/stdlib/src/_nolibc/math_nolibc/sincos.c3 b/test/stdlib/src/_nolibc/math_nolibc/sincos.c3 index ebccdf3..eed3c63 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/sincos.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/sincos.c3 @@ -81,7 +81,7 @@ fn void sincosf(float x, float *sin, float *cos) @cname("__sincosf") @weak @nost default: // general argument reduction needed double y @noinit; - uint n = __rem_pio2f(x, &y); + int n = __rem_pio2f(x, &y); float s = __sindf(y); float c = __cosdf(y); switch (n & 3) diff --git a/test/stdlib/src/_nolibc/math_nolibc/tan.c3 b/test/stdlib/src/_nolibc/math_nolibc/tan.c3 index 8c5f0a1..91cee1b 100644 --- a/test/stdlib/src/_nolibc/math_nolibc/tan.c3 +++ b/test/stdlib/src/_nolibc/math_nolibc/tan.c3 @@ -35,8 +35,7 @@ fn double tan(double x) @cname("tan") @weak @nostrip default: // argument reduction double[2] y; - uint n = __rem_pio2(x, &y); - return __tan(y[0], y[1], n & 1); + return __tan(y[0], y[1], __rem_pio2(x, &y) & 1); } } @@ -93,8 +92,7 @@ fn float tanf(float x) @cname("tanf") @weak @nostrip default: // argument reduction double y; - uint n = __rem_pio2f(x, &y); - return __tandf(y, n & 1); + return __tandf(y, __rem_pio2f(x, &y) & 1); } } diff --git a/test/stdlib/src/_nolibc/nolibc.c3 b/test/stdlib/src/_nolibc/nolibc.c3 index bf1b541..9913bc9 100644 --- a/test/stdlib/src/_nolibc/nolibc.c3 +++ b/test/stdlib/src/_nolibc/nolibc.c3 @@ -1,4 +1,4 @@ -module nolibc @if(env::NO_LIBC); +module std::nolibc @if(env::NO_LIBC); fn void __stack_chk_fail() @cname("__stack_chk_fail") @nostrip @noreturn @weak { diff --git a/test/stdlib/src/ascii.c3 b/test/stdlib/src/ascii.c3 deleted file mode 100644 index 373b72a..0000000 --- a/test/stdlib/src/ascii.c3 +++ /dev/null @@ -1,42 +0,0 @@ -<* This module is scheduled for removal, use std::core::ascii *> -module std::ascii; - -macro bool in_range_m(c, start, len) => (uint)(c - start) < len; -macro bool is_lower_m(c) => in_range_m(c, 0x61, 26); -macro bool is_upper_m(c) => in_range_m(c, 0x41, 26); -macro bool is_digit_m(c) => in_range_m(c, 0x30, 10); -macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2); -macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8); -macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c); -macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26); -macro bool is_print_m(c) => in_range_m(c, 0x20, 95); -macro bool is_graph_m(c) => in_range_m(c, 0x21, 94); -macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20; -macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c); -macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c); -macro bool is_blank_m(c) => c == 0x20 || c == 0x9; -macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f; -macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c; -macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c; - -fn bool in_range(char c, char start, char len) => in_range_m(c, start, len); - -fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len); - -fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len); -fn bool uint.is_lower(uint c) @deprecated => is_lower_m(c); -fn bool uint.is_upper(uint c) @deprecated => is_upper_m(c); -fn bool uint.is_digit(uint c) @deprecated => is_digit_m(c); -fn bool uint.is_bdigit(uint c) @deprecated => is_bdigit_m(c); -fn bool uint.is_odigit(uint c) @deprecated => is_odigit_m(c); -fn bool uint.is_xdigit(uint c) @deprecated => is_xdigit_m(c); -fn bool uint.is_alpha(uint c) @deprecated => is_alpha_m(c); -fn bool uint.is_print(uint c) @deprecated => is_print_m(c); -fn bool uint.is_graph(uint c) @deprecated => is_graph_m(c); -fn bool uint.is_space(uint c) @deprecated => is_space_m(c); -fn bool uint.is_alnum(uint c) @deprecated => is_alnum_m(c); -fn bool uint.is_punct(uint c) @deprecated => is_punct_m(c); -fn bool uint.is_blank(uint c) @deprecated => is_blank_m(c); -fn bool uint.is_cntrl(uint c) @deprecated => is_cntrl_m(c); -fn uint uint.to_lower(uint c) @deprecated => (uint)to_lower_m(c); -fn uint uint.to_upper(uint c) @deprecated => (uint)to_upper_m(c); diff --git a/test/stdlib/src/atomic.c3 b/test/stdlib/src/atomic.c3 index 99a6a77..3134637 100644 --- a/test/stdlib/src/atomic.c3 +++ b/test/stdlib/src/atomic.c3 @@ -104,10 +104,10 @@ macro bool @is_native_atomic_value(#value) @private macro bool is_native_atomic_type($Type) { - $if $Type.sizeof > void*.sizeof: + $if $Type::size > void*::size: return false; $else - $switch $Type.kindof: + $switch $Type::kind: $case SIGNED_INT: $case UNSIGNED_INT: $case POINTER: @@ -117,7 +117,7 @@ macro bool is_native_atomic_type($Type) return true; $case TYPEDEF: $case CONSTDEF: - return is_native_atomic_type($Type.inner); + return is_native_atomic_type($Type::inner); $default: return false; $endswitch @@ -136,10 +136,10 @@ macro bool is_native_atomic_type($Type) @require $defined(*ptr + y) : "+ must be defined between the values." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { $if $alignment == 0: - $alignment = $typeof(*ptr).sizeof; + $alignment = $typeof(*ptr)::size; $endif return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -156,10 +156,10 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil @require $defined(*ptr - y) : "- must be defined between the values." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { $if $alignment == 0: - $alignment = $typeof(*ptr).sizeof; + $alignment = $typeof(*ptr)::size; $endif return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -256,7 +256,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) @require $defined(*ptr | y) : "| must be defined between the values." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -273,7 +273,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile @require $defined(*ptr ^ y) : "^ must be defined between the values." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -290,7 +290,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil @require $defined(*ptr ^ y) : "& must be defined between the values." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -445,10 +445,10 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT) @require $defined(*ptr > y) : "Only values that are comparable with > may be used" @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { $if $alignment == 0: - $alignment = $typeof(*ptr).sizeof; + $alignment = $typeof(*ptr)::size; $endif return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment); } @@ -464,10 +464,10 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil @require $defined(*ptr > y) : "Only values that are comparable with > may be used" @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." *> -macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0) +macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, sz $alignment = 0) { $if $alignment == 0: - $alignment = $typeof(*ptr).sizeof; + $alignment = $typeof(*ptr)::size; $endif return $$atomic_fetch_min(ptr, y, $volatile, $ordering.ordinal, $alignment); } diff --git a/test/stdlib/src/bits.c3 b/test/stdlib/src/bits.c3 index cd79c60..5040daf 100644 --- a/test/stdlib/src/bits.c3 +++ b/test/stdlib/src/bits.c3 @@ -10,97 +10,97 @@ macro reverse(i) => $$bitreverse(i); *> macro bswap(i) @builtin => $$bswap(i); -macro uint[<*>].popcount(self) => $$popcount(self); -macro uint[<*>].ctz(self) => $$ctz(self); -macro uint[<*>].clz(self) => $$clz(self); +macro int uint[<*>].popcount(self) => (int)$$popcount(self); +macro int uint[<*>].ctz(self) => (int)$$ctz(self); +macro int uint[<*>].clz(self) => (int)$$clz(self); macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift); macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift); macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift); macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift); -macro int[<*>].popcount(self) => $$popcount(self); -macro int[<*>].ctz(self) => $$ctz(self); -macro int[<*>].clz(self) => $$clz(self); +macro int int[<*>].popcount(self) => (int)$$popcount(self); +macro int int[<*>].ctz(self) => (int)$$ctz(self); +macro int int[<*>].clz(self) => (int)$$clz(self); macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift); macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift); macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift); macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift); -macro ushort[<*>].popcount(self) => $$popcount(self); -macro ushort[<*>].ctz(self) => $$ctz(self); -macro ushort[<*>].clz(self) => $$clz(self); +macro int ushort[<*>].popcount(self) => (int)$$popcount(self); +macro int ushort[<*>].ctz(self) => (int)$$ctz(self); +macro int ushort[<*>].clz(self) => (int)$$clz(self); macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift); macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift); macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift); macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift); -macro short[<*>].popcount(self) => $$popcount(self); -macro short[<*>].ctz(self) => $$ctz(self); -macro short[<*>].clz(self) => $$clz(self); +macro int short[<*>].popcount(self) => (int)$$popcount(self); +macro int short[<*>].ctz(self) => (int)$$ctz(self); +macro int short[<*>].clz(self) => (int)$$clz(self); macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift); macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift); macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift); macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift); -macro char[<*>].popcount(self) => $$popcount(self); -macro char[<*>].ctz(self) => $$ctz(self); -macro char[<*>].clz(self) => $$clz(self); +macro int char[<*>].popcount(self) => (int)$$popcount(self); +macro int char[<*>].ctz(self) => (int)$$ctz(self); +macro int char[<*>].clz(self) => (int)$$clz(self); macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift); macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift); macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift); macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift); -macro ichar[<*>].popcount(self) => $$popcount(self); -macro ichar[<*>].ctz(self) => $$ctz(self); -macro ichar[<*>].clz(self) => $$clz(self); +macro int ichar[<*>].popcount(self) => (int)$$popcount(self); +macro int ichar[<*>].ctz(self) => (int)$$ctz(self); +macro int ichar[<*>].clz(self) => (int)$$clz(self); macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift); macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift); macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift); macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift); -macro ulong[<*>].popcount(self) => $$popcount(self); -macro ulong[<*>].ctz(self) => $$ctz(self); -macro ulong[<*>].clz(self) => $$clz(self); +macro int ulong[<*>].popcount(self) => (int)$$popcount(self); +macro int ulong[<*>].ctz(self) => (int)$$ctz(self); +macro int ulong[<*>].clz(self) => (int)$$clz(self); macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift); macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift); macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift); macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift); -macro long[<*>].popcount(self) => $$popcount(self); -macro long[<*>].ctz(self) => $$ctz(self); -macro long[<*>].clz(self) => $$clz(self); +macro int long[<*>].popcount(self) => (int)$$popcount(self); +macro int long[<*>].ctz(self) => (int)$$ctz(self); +macro int long[<*>].clz(self) => (int)$$clz(self); macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift); macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift); macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift); macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift); -macro uint128[<*>].popcount(self) => $$popcount(self); -macro uint128[<*>].ctz(self) => $$ctz(self); -macro uint128[<*>].clz(self) => $$clz(self); +macro int uint128[<*>].popcount(self) => (int)$$popcount(self); +macro int uint128[<*>].ctz(self) => (int)$$ctz(self); +macro int uint128[<*>].clz(self) => (int)$$clz(self); macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift); macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift); macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift); macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift); -macro int128[<*>].popcount(self) => $$popcount(self); -macro int128[<*>].ctz(self) => $$ctz(self); -macro int128[<*>].clz(self) => $$clz(self); +macro int int128[<*>].popcount(self) => (int)$$popcount(self); +macro int int128[<*>].ctz(self) => (int)$$ctz(self); +macro int int128[<*>].clz(self) => (int)$$clz(self); macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift); macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift); macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift); macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift); -macro uint.popcount(self) => $$popcount(self); -macro uint.ctz(self) => $$ctz(self); -macro uint.clz(self) => $$clz(self); +macro int uint.popcount(self) => (int)$$popcount(self); +macro int uint.ctz(self) => (int)$$ctz(self); +macro int uint.clz(self) => (int)$$clz(self); macro uint uint.fshl(hi, uint lo, uint shift) => $$fshl(hi, lo, shift); macro uint uint.fshr(hi, uint lo, uint shift) => $$fshr(hi, lo, shift); macro uint uint.rotl(self, uint shift) => $$fshl(self, self, shift); macro uint uint.rotr(self, uint shift) => $$fshr(self, self, shift); -macro int.popcount(self) => $$popcount(self); -macro int.ctz(self) => $$ctz(self); -macro int.clz(self) => $$clz(self); +macro int int.popcount(self) => (int)$$popcount(self); +macro int int.ctz(self) => (int)$$ctz(self); +macro int int.clz(self) => (int)$$clz(self); macro int int.fshl(hi, int lo, int shift) => $$fshl(hi, lo, shift); macro int int.fshr(hi, int lo, int shift) => $$fshr(hi, lo, shift); macro int int.rotl(self, int shift) => $$fshl(self, self, shift); @@ -114,57 +114,57 @@ macro ushort ushort.fshr(hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift); macro ushort ushort.rotl(self, ushort shift) => $$fshl(self, self, shift); macro ushort ushort.rotr(self, ushort shift) => $$fshr(self, self, shift); -macro short.popcount(self) => $$popcount(self); -macro short.ctz(self) => $$ctz(self); -macro short.clz(self) => $$clz(self); +macro int short.popcount(self) => (int)$$popcount(self); +macro int short.ctz(self) => (int)$$ctz(self); +macro int short.clz(self) => (int)$$clz(self); macro short short.fshl(hi, short lo, short shift) => $$fshl(hi, lo, shift); macro short short.fshr(hi, short lo, short shift) => $$fshr(hi, lo, shift); macro short short.rotl(self, short shift) => $$fshl(self, self, shift); macro short short.rotr(self, short shift) => $$fshr(self, self, shift); -macro char.popcount(self) => $$popcount(self); -macro char.ctz(self) => $$ctz(self); -macro char.clz(self) => $$clz(self); +macro int char.popcount(self) => (int)$$popcount(self); +macro int char.ctz(self) => (int)$$ctz(self); +macro int char.clz(self) => (int)$$clz(self); macro char char.fshl(hi, char lo, char shift) => $$fshl(hi, lo, shift); macro char char.fshr(hi, char lo, char shift) => $$fshr(hi, lo, shift); macro char char.rotl(self, char shift) => $$fshl(self, self, shift); macro char char.rotr(self, char shift) => $$fshr(self, self, shift); -macro ichar.popcount(self) => $$popcount(self); -macro ichar.ctz(self) => $$ctz(self); -macro ichar.clz(self) => $$clz(self); +macro int ichar.popcount(self) => (int)$$popcount(self); +macro int ichar.ctz(self) => (int)$$ctz(self); +macro int ichar.clz(self) => (int)$$clz(self); macro ichar ichar.fshl(hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift); macro ichar ichar.fshr(hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift); macro ichar ichar.rotl(self, ichar shift) => $$fshl(self, self, shift); macro ichar ichar.rotr(self, ichar shift) => $$fshr(self, self, shift); -macro ulong.popcount(self) => $$popcount(self); -macro ulong.ctz(self) => $$ctz(self); -macro ulong.clz(self) => $$clz(self); +macro int ulong.popcount(self) => (int)$$popcount(self); +macro int ulong.ctz(self) => (int)$$ctz(self); +macro int ulong.clz(self) => (int)$$clz(self); macro ulong ulong.fshl(hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift); macro ulong ulong.fshr(hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift); macro ulong ulong.rotl(self, ulong shift) => $$fshl(self, self, shift); macro ulong ulong.rotr(self, ulong shift) => $$fshr(self, self, shift); -macro long.popcount(self) => $$popcount(self); -macro long.ctz(self) => $$ctz(self); -macro long.clz(self) => $$clz(self); +macro int long.popcount(self) => (int)$$popcount(self); +macro int long.ctz(self) => (int)$$ctz(self); +macro int long.clz(self) => (int)$$clz(self); macro long long.fshl(hi, long lo, long shift) => $$fshl(hi, lo, shift); macro long long.fshr(hi, long lo, long shift) => $$fshr(hi, lo, shift); macro long long.rotl(self, long shift) => $$fshl(self, self, shift); macro long long.rotr(self, long shift) => $$fshr(self, self, shift); -macro uint128.popcount(self) => $$popcount(self); -macro uint128.ctz(self) => $$ctz(self); -macro uint128.clz(self) => $$clz(self); +macro int uint128.popcount(self) => (int)$$popcount(self); +macro int uint128.ctz(self) => (int)$$ctz(self); +macro int uint128.clz(self) => (int)$$clz(self); macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift); macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift); macro uint128 uint128.rotl(self, uint128 shift) => $$fshl(self, self, shift); macro uint128 uint128.rotr(self, uint128 shift) => $$fshr(self, self, shift); -macro int128.popcount(self) => $$popcount(self); -macro int128.ctz(self) => $$ctz(self); -macro int128.clz(self) => $$clz(self); +macro int int128.popcount(self) => (int)$$popcount(self); +macro int int128.ctz(self) => (int)$$ctz(self); +macro int int128.clz(self) => (int)$$clz(self); macro int128 int128.fshl(hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift); macro int128 int128.fshr(hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift); macro int128 int128.rotl(self, int128 shift) => $$fshl(self, self, shift); diff --git a/test/stdlib/src/collections/anylist.c3 b/test/stdlib/src/collections/anylist.c3 index ed4dab2..88d635e 100644 --- a/test/stdlib/src/collections/anylist.c3 +++ b/test/stdlib/src/collections/anylist.c3 @@ -97,9 +97,9 @@ macro AnyList.pop_first(&self, $Type) @param $Type : "The type of the element" @return "The element at the index" @return? TYPE_MISMATCH, NO_MORE_ELEMENT - @require index < self.size : "Index out of range" + @require index < self.size && index >= 0 : "Index out of range" *> -macro AnyList.get(&self, usz index, $Type) +macro AnyList.get(&self, sz index, $Type) { return *anycast(self.entries[index], $Type); } @@ -110,13 +110,13 @@ macro AnyList.get(&self, usz index, $Type) @param index : "The index of the element to retrieve" @return "The element at the index" @return? TYPE_MISMATCH, NO_MORE_ELEMENT - @require index < self.size : "Index out of range" + @require index < self.size && index >= 0 : "Index out of range" *> -fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index); +fn any AnyList.get_any(&self, sz index) @inline @operator([]) => InterfaceList {any}.get(self, index); <* Return the length of the list. @return "The number of elements in the list" *> -fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self); +fn sz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self); diff --git a/test/stdlib/src/collections/bitset.c3 b/test/stdlib/src/collections/bitset.c3 index 26b1f58..1583afa 100644 --- a/test/stdlib/src/collections/bitset.c3 +++ b/test/stdlib/src/collections/bitset.c3 @@ -3,10 +3,11 @@ // a copy of which can be found in the LICENSE_STDLIB file. <* @require SIZE > 0 : "The size of the bitset in bits must be at least 1" + @require SIZE / (uint::size * 8) < uint::max *> module std::collections::bitset ; -const BITS = uint.sizeof * 8; +const BITS = uint::size * 8; const SZ = (SIZE + BITS - 1) / BITS; struct BitSet @@ -17,9 +18,9 @@ struct BitSet <* @return "The number of bits set" *> -fn usz BitSet.cardinality(&self) +fn sz BitSet.cardinality(&self) { - usz n; + sz n; foreach (x : self.data) { n += x.popcount(); @@ -32,13 +33,13 @@ fn usz BitSet.cardinality(&self) @param i : "The index to set" - @require i < SIZE : "Index was out of range" + @require i < SIZE && i >= 0 : "Index was out of range" *> -fn void BitSet.set(&self, usz i) +fn void BitSet.set(&self, sz i) { - usz q = i / BITS; - usz r = i % BITS; - self.data[q] |= 1 << r; + sz q = i / BITS; + uint r = (uint)(i % BITS); + self.data[q] |= 1u << r; } <* @@ -69,7 +70,7 @@ fn BitSet BitSet.xor(&self, BitSet set) @operator(^) <* Perform or over all bits, returning a new bit set. - @param set : "The bit set to xor with" + @param set : "The bit set to or with" @return "The resulting bit set" *> fn BitSet BitSet.or(&self, BitSet set) @operator(|) @@ -82,7 +83,7 @@ fn BitSet BitSet.or(&self, BitSet set) @operator(|) <* Perform or over all bits, mutating itself - @param set : "The bit set to xor with" + @param set : "The bit set to or with" @return "The resulting bit set" *> macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=) @@ -94,7 +95,7 @@ macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=) <* Perform & over all bits, returning a new bit set. - @param set : "The bit set to xor with" + @param set : "The bit set to & with" @return "The resulting bit set" *> fn BitSet BitSet.and(&self, BitSet set) @operator(&) @@ -107,7 +108,7 @@ fn BitSet BitSet.and(&self, BitSet set) @operator(&) <* Perform & over all bits, mutating itself. - @param set : "The bit set to xor with" + @param set : "The bit set to & with" @return "The resulting bit set" *> macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=) @@ -121,13 +122,13 @@ macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=) @param i : "The index to set" - @require i < SIZE : "Index was out of range" + @require i < SIZE && i >= 0 : "Index was out of range" *> -fn void BitSet.unset(&self, usz i) +fn void BitSet.unset(&self, sz i) { - usz q = i / BITS; - usz r = i % BITS; - self.data[q] &= ~(1 << r); + sz q = i / BITS; + uint r = (uint)(i % BITS); + self.data[q] &= ~(1u << r); } <* @@ -135,14 +136,14 @@ fn void BitSet.unset(&self, usz i) @param i : "The index of the bit" - @require i < SIZE : "Index was out of range" + @require i < SIZE && i >= 0 : "Index was out of range" @pure *> -fn bool BitSet.get(&self, usz i) @operator([]) @inline +fn bool BitSet.get(&self, sz i) @operator([]) @inline { - usz q = i / BITS; - usz r = i % BITS; - return self.data[q] & (1 << r) != 0; + sz q = i / BITS; + uint r = (uint)(i % BITS); + return self.data[q] & (1u << r) != 0; } <* @@ -150,7 +151,7 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline @pure *> -fn usz BitSet.len(&self) @operator(len) @inline +fn sz BitSet.len(&self) @operator(len) @inline { return SZ * BITS; } @@ -161,21 +162,21 @@ fn usz BitSet.len(&self) @operator(len) @inline @param i : "The index of the bit" @param value : "The value to set the bit to" - @require i < SIZE : "Index was out of range" + @require i < SIZE && i >= 0 : "Index was out of range" *> -fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline +fn void BitSet.set_bool(&self, sz i, bool value) @operator([]=) @inline { if (value) return self.set(i); self.unset(i); } <* - @require Type.kindof == UNSIGNED_INT + @require Type::kind == UNSIGNED_INT *> module std::collections::growablebitset ; import std::collections::list; -const BITS = Type.sizeof * 8; +const BITS = Type::size * 8; alias GrowableBitSetList = List{Type}; @@ -185,28 +186,38 @@ struct GrowableBitSet } <* - @param initial_capacity - @param [&inout] allocator : "The allocator to use, defaults to the heap allocator" + @param initial_capacity : "The initial capcity of the underlying list" + @param [&inout] allocator : "The allocator to use" + + @require initial_capacity > 0 : "The initial capacity must be at least 1" *> -fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1) +fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, sz initial_capacity = 1) { self.data.init(allocator, initial_capacity); return self; } -fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1) +<* + @param initial_capacity : "The initial capcity of the underlying list" + + @require initial_capacity > 0 : "The initial capacity must be at least 1" +*> +fn GrowableBitSet* GrowableBitSet.tinit(&self, sz initial_capacity = 1) { return self.init(tmem, initial_capacity) @inline; } +<* + Free all memory associated with the set. +*> fn void GrowableBitSet.free(&self) { self.data.free(); } -fn usz GrowableBitSet.cardinality(&self) +fn sz GrowableBitSet.cardinality(&self) { - usz n; + sz n; foreach (x : self.data) { n += x.popcount(); @@ -214,43 +225,57 @@ fn usz GrowableBitSet.cardinality(&self) return n; } -fn void GrowableBitSet.set(&self, usz i) +<* + Set a bit in the bitset. + + @param i : "The index to set" + + @require i >= 0 : "Index was out of range" +*> +fn void GrowableBitSet.set(&self, sz i) { - usz q = i / BITS; - usz r = i % BITS; - usz current_len = self.data.len(); + sz q = i / BITS; + uint r = (uint)(i % BITS); + sz current_len = self.data.len(); while (q >= current_len) { self.data.push(0); current_len++; } - self.data.set(q, self.data[q] | (1 << r)); + self.data.set(q, self.data[q] | (1u << r)); } -fn void GrowableBitSet.unset(&self, usz i) +<* + Clear a bit in the bitset. + + @param i : "The index to clear" + + @require i >= 0 : "Index was out of range" +*> +fn void GrowableBitSet.unset(&self, sz i) { - usz q = i / BITS; - usz r = i % BITS; + sz q = i / BITS; + uint r = (uint)(i % BITS); if (q >= self.data.len()) return; - self.data.set(q, self.data[q] &~ (1 << r)); + self.data.set(q, self.data[q] &~ (1u << r)); } -fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline +fn bool GrowableBitSet.get(&self, sz i) @operator([]) @inline { - usz q = i / BITS; - usz r = i % BITS; + sz q = i / BITS; + uint r = (uint)(i % BITS); if (q >= self.data.len()) return false; - return self.data[q] & (1 << r) != 0; + return self.data[q] & (1u << r) != 0; } -fn usz GrowableBitSet.len(&self) @operator(len) +fn sz GrowableBitSet.len(&self) @operator(len) { - usz n = self.data.len() * BITS; - if (n > 0) n -= (usz)self.data[^1].clz(); + sz n = self.data.len() * BITS; + if (n > 0) n -= (sz)self.data[^1].clz(); return n; } -fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline +fn void GrowableBitSet.set_bool(&self, sz i, bool value) @operator([]=) @inline { if (value) return self.set(i); self.unset(i); diff --git a/test/stdlib/src/collections/deque.c3 b/test/stdlib/src/collections/deque.c3 new file mode 100644 index 0000000..40c5923 --- /dev/null +++ b/test/stdlib/src/collections/deque.c3 @@ -0,0 +1,416 @@ +module std::collections::deque ; +import std::io, std::math; + +const sz DEFAULT_CAPACITY = 16; + +<* + Deque is a dynamically resizable double-ended queue/ring-buffer. + Being double-ended means that elements can be pushed or popped from + either end of the same memory block, in any order, + allowing the structure to act as both a stack and a queue. +*> +struct Deque (Printable) +{ + Type* entries; + sz capacity; + sz start; + sz size; + Allocator allocator; +} + +<* + @require capacity > 0 : "Capacity should be greater than 0" + @require capacity < sz::max / Type::size : "Capacity too great" +*> +fn void Deque.init(&self, Allocator allocator, sz capacity = DEFAULT_CAPACITY) +{ + *self = { }; + self.entries = alloc::alloc_array(allocator, Type, capacity); + self.capacity = capacity; + self.allocator = allocator; +} + +<* + @require capacity > 0 : "Capacity should be greater than 0" + @require capacity < sz::max / Type::size : "Capacity too great" +*> +fn void Deque.tinit(&self, sz capacity = DEFAULT_CAPACITY) +{ + *self = { }; + self.entries = mem::temp_array(Type, capacity); + self.capacity = capacity; + self.allocator = tmem; +} + +<* + Push an element to the back of the deque. +*> +fn void Deque.push(&self, Type element) +{ + self.reserve(1); + sz idx = next_back_idx(self); + self.entries[idx] = element; + self.size++; +} + +<* + Push an element to the front of the deque. +*> +fn void Deque.push_front(&self, Type element) +{ + self.reserve(1); + self.start = next_front_idx(self); + self.entries[self.start] = element; + self.size++; +} + +<* + Push multiple elements to the back of the deque. +*> +fn void Deque.push_all(&self, Type[] elements) +{ + foreach (el : elements) + { + self.push(el); + } +} + +<* + Push multiple elements to the front of the deque. +*> +fn void Deque.push_front_all(&self, Type[] elements) +{ + foreach (el : elements) + { + self.push_front(el); + } +} + +<* + Pop an element from the back of the deque. + + @return? NO_MORE_ELEMENT : "If size is 0" +*> +fn Type? Deque.pop(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + self.size--; + sz idx = next_back_idx(self); + return self.entries[idx]; +} + +<* + Pop an element from the front of the deque. + + @return? NO_MORE_ELEMENT : "If size is 0" +*> +fn Type? Deque.pop_first(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + Type elem = self.entries[self.start]; + self.start = wrap_inc(self.start, self.capacity); + self.size--; + return elem; +} + +<* + Pop multiple elements from the front of the deque into 'target'. + Count of the popped elements calculated by taking the 'min' + on the 'target' length and deque's internal length +*> +fn Type[] Deque.pop_first_into(&self, Type[] target) +{ + if (@unlikely(self.is_empty())) return target[:0]; + sz len = math::min(target.len, self.size); + foreach (&el : target[:len]) + { + *el = _pop_front(self); + } + return target[:len]; +} + +<* + Pop multiple elements from the back of the deque into 'target'. + Count of the popped elements calculated by taking the 'min' + on the 'target' length and deque's internal length + +*> +fn Type[] Deque.pop_into(&self, Type[] target) +{ + if (@unlikely(self.is_empty())) return target[:0]; + sz len = math::min(target.len, self.size); + foreach (&el : target[:len]) + { + *el = _pop_back(self); + } + return target[:len]; +} + +<* + Reserve enough space in the queue for at least the specified value. + + @param added : "Specifies how many extra capacity is required at least" +*> +fn void Deque.reserve(&self, sz added) +{ + sz diff = (self.size + added) - self.capacity; + if (@unlikely(diff <= 0)) return; + sz req_capacity = self.capacity + diff; + + assert(req_capacity > 0); + assert(self.capacity < sz::max / 2); + + sz new_capacity = self.capacity * 2; + while (new_capacity < req_capacity) + { + new_capacity *= 2; + } + _grow(self, new_capacity); +} + +<* + Makes deque's internal buffer contiguous and sets the 'capacity' to 'size'. + + @require self.capacity > self.size : "Capacity should be bigger than size" +*> +fn void Deque.shrink(&self) +{ + self.normalize(); + self.capacity = self.size; + self.entries = alloc::realloc(self.allocator, self.entries, self.capacity * Type::size); +} + +<* + Makes deque's internal buffer contiguous. + Same as 'shrink' but preserves current capacity. +*> +fn void Deque.normalize(&self) +{ + if (self.size < 1 || self.start < 1) return; + // Simple case [ _, _, 1, 2, 3] + if (self.start + self.size <= self.capacity) + { + self.entries[:self.size] = self.entries[self.start:self.size]; + self.start = 0; + return; + } + @pool() + { + Type[] tbuf = mem::temp_array(Type, self.start); + tbuf[..] = self.entries[0:self.start]; + sz after_start = self.capacity - self.start; + self.entries[:after_start] = self.entries[self.start:after_start]; + self.entries[after_start:self.start] = tbuf[..]; + self.start = 0; + }; + +} + +<* + Reset the deque's length and offset to zero, letting it write new elements over + old memory, in effect clearing the accessible contents. +*> +fn void Deque.clear(&self) @inline +{ + self.size = 0; + self.start = 0; +} + +<* + Free the memory that has been dynamically allocated for deque. +*> +fn void Deque.free(&self) +{ + if (@unlikely(!self.entries)) return; + + alloc::free(self.allocator, self.entries); + self.entries = null; + self.clear(); +} + +// --- Utilities --- + +fn sz Deque.remain_space(Deque* self) @inline +{ + return self.capacity - self.size; +} + +fn bool Deque.is_empty(Deque* self) @inline +{ + return self.size == 0; +} + +<* + @require i < self.size && i >= 0 && j < self.size && j >= 0 : `Access out of bounds` +*> +fn void Deque.swap(&self, sz i, sz j) +{ + i = wrap_add(self.start, i, self.capacity); + j = wrap_add(self.start, j, self.capacity); + @swap(self.entries[i], self.entries[j]); +} + +<* + Get an element at the front of the deque. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type Deque.first(&self) @inline +{ + return self.entries[self.start]; +} + +<* + Get a reference to an element at the front of the deque. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type* Deque.first_ref(&self) @inline +{ + return &self.entries[self.start]; +} + +<* + Get an element at the back of the deque. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type Deque.last(&self) @inline +{ + return self.entries[curr_back_idx(self)]; +} + +<* + Get a reference to an element at the back of the deque. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type* Deque.last_ref(&self) @inline +{ + return &self.entries[curr_back_idx(self)]; +} + +<* + @require index >= 0 && index < self.size : `Access out of bounds` +*> +fn Type Deque.get(&self, sz index) @operator([]) @inline +{ + sz idx = wrap_add(self.start, index, self.capacity); + return self.entries[idx]; +} + +<* + @require index >= 0 && index < self.size : `Access out of bounds` +*> +fn Type* Deque.get_ref(&self, sz index) @operator(&[]) @inline +{ + sz idx = wrap_add(self.start, index, self.capacity); + return &self.entries[idx]; +} + +<* + @require index >= 0 && index < self.size : `Access out of bounds` +*> +fn void Deque.set(&self, sz index, Type value) @operator([]=) +{ + sz idx = wrap_add(self.start, index, self.capacity); + self.entries[idx] = value; +} + +fn sz Deque.len(&self) @operator(len) @inline +{ + return self.size; +} + +fn sz? Deque.to_format(&self, Formatter* fmt) @dynamic +{ + if (@unlikely(self.is_empty())) + { + return fmt.print("[]"); + } + + sz total = fmt.printf("[%s", self.entries[self.start])!; + sz curr_idx = wrap_inc(self.start, self.capacity); + + for (sz i = 0; i < self.size - 1; ++i) + { + total += fmt.printf(", %s", self.entries[curr_idx])!; + curr_idx = wrap_inc(curr_idx, self.capacity); + } + + return total + fmt.print("]"); +} + +// --- Private API --- + +fn void _grow(Deque* self, sz new_capacity) @private +{ + self.entries = alloc::realloc(self.allocator, self.entries, Type::size * new_capacity); + sz old_capacity = self.capacity; + + if (self.start + self.size > old_capacity) + { + sz diff = old_capacity - self.start; + self.entries[new_capacity - diff:diff] = self.entries[self.start:diff]; + self.start += new_capacity - old_capacity; + } + self.capacity = new_capacity; +} + +fn sz next_back_idx(Deque* self) @inline @private +{ + return wrap_add(self.start, self.size, self.capacity); +} + +fn sz curr_back_idx(Deque* self) @inline @private +{ + return wrap_add(self.start, self.size - 1, self.capacity); +} + +fn sz next_front_idx(Deque* self) @inline @private +{ + return wrap_dec(self.start, self.capacity); +} + +<* + More optimized version of 'pop_back', when we know that the size is not 0. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type _pop_back(Deque* self) @private +{ + self.size--; + sz idx = next_back_idx(self); + return self.entries[idx]; +} + +<* + More optimized version of 'pop_front', when we know that the size is not 0. + + @require self.size > 0 : "Size should be greater than 0" +*> +fn Type _pop_front(Deque* self) @private +{ + Type elem = self.entries[self.start]; + self.start = wrap_inc(self.start, self.capacity); + self.size--; + return elem; +} + +macro wrap_inc(sz val, sz boundary) @local +{ + return val == boundary - 1 ? 0 : val + 1; +} + +macro wrap_dec(sz val, sz boundary) @local +{ + return val == 0 ? boundary - 1 : val - 1; +} + +<* + @require add >= 0 +*> +macro wrap_add(sz val, sz add, sz boundary) @local +{ + return (val + add) % boundary; +} diff --git a/test/stdlib/src/collections/elastic_array.c3 b/test/stdlib/src/collections/elastic_array.c3 index 9d535a1..68c77ef 100644 --- a/test/stdlib/src/collections/elastic_array.c3 +++ b/test/stdlib/src/collections/elastic_array.c3 @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved. +// Copyright (c) 2021-2026 Christoffer Lerno. All rights reserved. // Use of self source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. <* @@ -10,40 +10,25 @@ import std::io, std::math, std::collections::list_common; alias ElementPredicate = fn bool(Type *type); alias ElementTest = fn bool(Type *type, any context); const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); -const ELEMENT_IS_POINTER = Type.kindof == POINTER; -macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT; +const ELEMENT_IS_POINTER = Type::kind == POINTER; +macro bool type_is_overaligned() => Type::alignment > mem::DEFAULT_MEM_ALIGNMENT; struct ElasticArray (Printable) { - usz size; + sz size; Type[MAX_SIZE] entries; } -fn usz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic +fn sz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic { - switch (self.size) - { - case 0: - return formatter.print("[]")!; - case 1: - return formatter.printf("[%s]", self.entries[0])!; - default: - usz n = formatter.print("[")!; - foreach (i, element : self.entries[:self.size]) - { - if (i != 0) formatter.print(", ")!; - n += formatter.printf("%s", element)!; - } - n += formatter.print("]")!; - return n; - } + return formatter.printf("%s", self.entries[:self.size]); } -fn String ElasticArray.to_tstring(&self) -{ - return string::tformat("%s", *self); -} +<* + Try to add an element. Will fail if it is full. + @return? mem::OUT_OF_MEMORY +*> fn void? ElasticArray.push_try(&self, Type element) @inline { if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~; @@ -80,14 +65,28 @@ fn Type? ElasticArray.pop_first(&self) } <* - @require index < self.size + Remove an element at a particular place. + + @require index < self.size && index >= 0 : "The index is out of bounds" *> -fn void ElasticArray.remove_at(&self, usz index) +fn void ElasticArray.remove_at(&self, sz index) { if (!--self.size || index == self.size) return; self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size]; } +<* + Removes an element at "index" by copying the last element to this slot and reducing + the length by 1. + + @require index < self.size && index >= 0 : "Index out of bounds" +*> +fn void ElasticArray.remove_unordered_at(&self, sz index) +{ + sz new_size = --self.size; + if (!new_size || index == new_size) return; + self.entries[index] = self.entries[new_size]; +} <* @require other_list.size + self.size <= MAX_SIZE *> @@ -104,7 +103,7 @@ fn void ElasticArray.add_all(&self, ElasticArray* other_list) Add as many elements as possible to the new array, returning the number of elements that didn't fit. *> -fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list) +fn sz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list) { if (!other_list.size) return 0; foreach (i, &value : other_list) @@ -115,22 +114,6 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list) return 0; } -<* - Add as many values from this array as possible, returning the - number of elements that didn't fit. - - @param [in] array -*> -fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit") -{ - if (!array.len) return 0; - foreach (i, &value : array) - { - if (self.size == MAX_SIZE) return array.len - i; - self.entries[self.size++] = *value; - } - return 0; -} <* Add as many values from this array as possible, returning the @@ -138,7 +121,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use pus @param [in] array *> -fn usz ElasticArray.push_all_to_limit(&self, Type[] array) +fn sz ElasticArray.push_all_to_limit(&self, Type[] array) { if (!array.len) return 0; foreach (i, &value : array) @@ -149,21 +132,6 @@ fn usz ElasticArray.push_all_to_limit(&self, Type[] array) return 0; } -<* - Add the values of an array to this list. - - @param [in] array - @require array.len + self.size <= MAX_SIZE : `Size would exceed max.` - @ensure self.size >= array.len -*> -fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all") -{ - if (!array.len) return; - foreach (&value : array) - { - self.entries[self.size++] = *value; - } -} <* Add the values of an array to this list. @@ -237,9 +205,9 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline } <* - @require index <= self.size + @require index <= self.size && index >= 0 : "Index was out of range" *> -fn void? ElasticArray.insert_at_try(&self, usz index, Type value) +fn void? ElasticArray.insert_at_try(&self, sz index, Type value) { if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~; self.insert_at(index, value); @@ -247,11 +215,11 @@ fn void? ElasticArray.insert_at_try(&self, usz index, Type value) <* @require self.size < MAX_SIZE : `List would exceed max size` - @require index <= self.size + @require index <= self.size && index >= 0 : "Index was out of range" *> -fn void ElasticArray.insert_at(&self, usz index, Type type) +fn void ElasticArray.insert_at(&self, sz index, Type type) { - for (usz i = self.size; i > index; i--) + for (sz i = self.size; i > index; i--) { self.entries[i] = self.entries[i - 1]; } @@ -260,9 +228,9 @@ fn void ElasticArray.insert_at(&self, usz index, Type type) } <* - @require index < self.size + @require index <= self.size && index >= 0 : "Index was out of range" *> -fn void ElasticArray.set_at(&self, usz index, Type type) +fn void ElasticArray.set_at(&self, sz index, Type type) { self.entries[index] = type; } @@ -296,22 +264,25 @@ fn bool ElasticArray.is_empty(&self) @inline return !self.size; } -fn usz ElasticArray.byte_size(&self) @inline +fn sz ElasticArray.byte_size(&self) @inline { - return Type.sizeof * self.size; + return Type::size * self.size; } -fn usz ElasticArray.len(&self) @operator(len) @inline +fn sz ElasticArray.len(&self) @operator(len) @inline { return self.size; } -fn Type ElasticArray.get(&self, usz index) @inline +<* + @require index <= self.size && index >= 0 : "Index was out of range" +*> +fn Type ElasticArray.get(&self, sz index) @inline { return self.entries[index]; } -fn void ElasticArray.swap(&self, usz i, usz j) +fn void ElasticArray.swap(&self, sz i, sz j) { @swap(self.entries[i], self.entries[j]); } @@ -320,7 +291,7 @@ fn void ElasticArray.swap(&self, usz i, usz j) @param filter : "The function to determine if it should be removed or not" @return "the number of deleted elements" *> -fn usz ElasticArray.remove_if(&self, ElementPredicate filter) +fn sz ElasticArray.remove_if(&self, ElementPredicate filter) { return list_common::list_remove_if(self, filter, false); } @@ -329,40 +300,43 @@ fn usz ElasticArray.remove_if(&self, ElementPredicate filter) @param selection : "The function to determine if it should be kept or not" @return "the number of deleted elements" *> -fn usz ElasticArray.retain_if(&self, ElementPredicate selection) +fn sz ElasticArray.retain_if(&self, ElementPredicate selection) { return list_common::list_remove_if(self, selection, true); } -fn usz ElasticArray.remove_using_test(&self, ElementTest filter, any context) +fn sz ElasticArray.remove_using_test(&self, ElementTest filter, any context) { return list_common::list_remove_using_test(self, filter, false, context); } -fn usz ElasticArray.retain_using_test(&self, ElementTest filter, any context) +fn sz ElasticArray.retain_using_test(&self, ElementTest filter, any context) { return list_common::list_remove_using_test(self, filter, true, context); } -macro Type ElasticArray.@item_at(&self, usz index) @operator([]) +macro Type ElasticArray.@item_at(&self, sz index) @operator([]) { return self.entries[index]; } -fn Type* ElasticArray.get_ref(&self, usz index) @operator(&[]) @inline +fn Type* ElasticArray.get_ref(&self, sz index) @operator(&[]) @inline { return &self.entries[index]; } -fn void ElasticArray.set(&self, usz index, Type value) @operator([]=) +<* + @require index <= self.size && index >= 0 : "Index was out of range" +*> +fn void ElasticArray.set(&self, sz index, Type value) @operator([]=) { self.entries[index] = value; } // Functions for equatable types -fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) +fn sz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) { foreach (i, v : self) { @@ -371,7 +345,7 @@ fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) return NOT_FOUND~; } -fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) +fn sz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) { foreach_r (i, v : self) { @@ -431,7 +405,7 @@ fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATAB @param value : "The value to remove" @return "the number of deleted elements." *> -fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +fn sz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) { return list_common::list_remove_item(self, value); } @@ -448,14 +422,14 @@ fn void ElasticArray.remove_all_from(&self, ElasticArray* other_list) @if(ELEMEN @param [&in] self @return "The number non-null values in the list" *> -fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER) +fn sz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER) { - usz vals = 0; + sz vals = 0; foreach (v : self) if (v) vals++; return vals; } -fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER) +fn sz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER) { return list_common::list_compact(self); } diff --git a/test/stdlib/src/collections/enummap.c3 b/test/stdlib/src/collections/enummap.c3 index be26f34..0247c72 100644 --- a/test/stdlib/src/collections/enummap.c3 +++ b/test/stdlib/src/collections/enummap.c3 @@ -1,13 +1,12 @@ <* - @require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap" - @require Enum.values.len > 0 : "Only non-empty enums may be used with enummap" + @require Enum::kind == TypeKind.ENUM : "Only enums may be used with an enummap" *> module std::collections::enummap ; import std::io; struct EnumMap (Printable) { - ValueType[Enum.values.len] values; + ValueType[Enum::len] values; } fn void EnumMap.init(&self, ValueType init_value) @@ -18,13 +17,13 @@ fn void EnumMap.init(&self, ValueType init_value) } } -fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic +fn sz? EnumMap.to_format(&self, Formatter* formatter) @dynamic { - usz n = formatter.print("{ ")!; + sz n = formatter.print("{ ")!; foreach (i, &value : self.values) { if (i != 0) formatter.print(", ")!; - n += formatter.printf("%s: %s", Enum.from_ordinal(i), *value)!; + n += formatter.printf("%s: %s", Enum::from_ordinal(i), *value)!; } n += formatter.print(" }")!; return n; @@ -34,7 +33,7 @@ fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic @return "The total size of this map, which is the same as the number of enum values" @pure *> -fn usz EnumMap.len(&self) @operator(len) @inline +fn sz EnumMap.len(&self) @operator(len) @inline { return self.values.len; } diff --git a/test/stdlib/src/collections/enumset.c3 b/test/stdlib/src/collections/enumset.c3 index 6d750f8..b75e0ad 100644 --- a/test/stdlib/src/collections/enumset.c3 +++ b/test/stdlib/src/collections/enumset.c3 @@ -3,12 +3,12 @@ // a copy of which can be found in the LICENSE_STDLIB file. <* - @require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset" + @require Enum::kind == TypeKind.ENUM : "Only enums may be used with an enumset" *> module std::collections::enumset ; import std::io; -const ENUM_COUNT @private = Enum.values.len; +const ENUM_COUNT @private = Enum::values.len; alias EnumSetType @private = $typefrom(type_for_enum_elements(ENUM_COUNT)); const IS_CHAR_ARRAY = ENUM_COUNT > 128; @@ -17,7 +17,7 @@ typedef EnumSet (Printable) = EnumSetType; fn void EnumSet.add(&self, Enum v) { $if IS_CHAR_ARRAY: - (*self)[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8)); + ((EnumSetType*)self).[(usz)v.ordinal / 8] |= (char)(1u << ((usz)v.ordinal % 8)); $else *self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v.ordinal); $endif @@ -25,18 +25,14 @@ fn void EnumSet.add(&self, Enum v) fn void EnumSet.clear(&self) { - $if IS_CHAR_ARRAY: - *self = {}; - $else - *self = 0; - $endif + *self = {}; } fn bool EnumSet.remove(&self, Enum v) { $if IS_CHAR_ARRAY: if (!self.has(v) @inline) return false; - (*self)[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8)); + ((EnumSetType*)self).[(usz)v.ordinal / 8] &= (char)~(1u << ((usz)v.ordinal % 8)); return true; $else EnumSetType old = (EnumSetType)*self; @@ -49,7 +45,7 @@ fn bool EnumSet.remove(&self, Enum v) fn bool EnumSet.has(&self, Enum v) { $if IS_CHAR_ARRAY: - return (bool)(((*self)[(usz)v.ordinal / 8] << ((usz)v.ordinal % 8)) & 0x01); + return (bool)((((EnumSetType*)self).[(usz)v.ordinal / 8] >> ((usz)v.ordinal % 8)) & 0x01); $else return ((EnumSetType)*self & (1u << (EnumSetType)v.ordinal)) != 0; $endif @@ -58,7 +54,7 @@ fn bool EnumSet.has(&self, Enum v) fn void EnumSet.add_all(&self, EnumSet s) { $if IS_CHAR_ARRAY: - foreach (i, c : s) (*self)[i] |= c; + foreach (i, c : s) ((EnumSetType*)self).[i] |= c; $else *self = (EnumSet)((EnumSetType)*self | (EnumSetType)s); $endif @@ -67,7 +63,7 @@ fn void EnumSet.add_all(&self, EnumSet s) fn void EnumSet.retain_all(&self, EnumSet s) { $if IS_CHAR_ARRAY: - foreach (i, c : s) (*self)[i] &= c; + foreach (i, c : (EnumSetType)s) ((EnumSetType*)self).[i] &= c; $else *self = (EnumSet)((EnumSetType)*self & (EnumSetType)s); $endif @@ -76,7 +72,7 @@ fn void EnumSet.retain_all(&self, EnumSet s) fn void EnumSet.remove_all(&self, EnumSet s) { $if IS_CHAR_ARRAY: - foreach (i, c : s) (*self)[i] &= ~c; + foreach (i, c : (EnumSetType)s) ((EnumSetType*)self).[i] &= ~c; $else *self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s); $endif @@ -119,19 +115,19 @@ fn EnumSet EnumSet.diff_of(&self, EnumSet s) fn EnumSet EnumSet.xor_of(&self, EnumSet s) { $if IS_CHAR_ARRAY: - EnumSet copy = *self; + EnumSetType copy = *(EnumSetType*)self; foreach (i, c : s) copy[i] ^= c; - return copy; + return (EnumSet)copy; $else return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s); $endif } -fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic +fn sz? EnumSet.to_format(&set, Formatter* formatter) @dynamic { - usz n = formatter.print("[")!; + sz n = formatter.print("[")!; bool found; - foreach (value : Enum.values) + foreach (value : Enum::values) { if (!set.has(value)) continue; if (found) n += formatter.print(", ")!; @@ -142,20 +138,20 @@ fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic return n; } -macro typeid type_for_enum_elements(usz $elements) @local +macro typeid type_for_enum_elements(sz $elements) @local { $switch: $case ($elements > 128): - return char[($elements + 7) / 8].typeid; + return char[($elements + 7) / 8]::typeid; $case ($elements > 64): - return uint128.typeid; + return uint128::typeid; $case ($elements > 32 || $$C_INT_SIZE > 32): - return ulong.typeid; + return ulong::typeid; $case ($elements > 16 || $$C_INT_SIZE > 16): - return uint.typeid; + return uint::typeid; $case ($elements > 8 || $$C_INT_SIZE > 8): - return ushort.typeid; + return ushort::typeid; $default: - return char.typeid; + return char::typeid; $endswitch } \ No newline at end of file diff --git a/test/stdlib/src/collections/hashmap.c3 b/test/stdlib/src/collections/hashmap.c3 index 68a742f..bab7ba5 100644 --- a/test/stdlib/src/collections/hashmap.c3 +++ b/test/stdlib/src/collections/hashmap.c3 @@ -8,10 +8,10 @@ module std::collections::map ; import std::math; import std::io @norecurse; -const uint DEFAULT_INITIAL_CAPACITY = 16; -const uint MAXIMUM_CAPACITY = 1u << 31; +const int DEFAULT_INITIAL_CAPACITY = 16; +const int MAXIMUM_CAPACITY = 1 << 30; const float DEFAULT_LOAD_FACTOR = 0.75; -const VALUE_IS_EQUATABLE = Value.is_eq; +const VALUE_IS_EQUATABLE = Value::has_equals; const bool COPY_KEYS = types::implements_copy(Key); const Allocator MAP_HEAP_ALLOCATOR = (Allocator)&dummy; @@ -31,9 +31,9 @@ struct HashMap (Printable) Entry*[] table; Allocator allocator; <* Last inserted LinkedEntry *> - uint count; + int count; <* Resize limit *> - uint threshold; + int threshold; float load_factor; } @@ -41,27 +41,27 @@ struct HashMap (Printable) <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashMap* HashMap.init(&self, Allocator allocator, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { capacity = math::next_power_of_2(capacity); self.allocator = allocator; self.load_factor = load_factor; - self.threshold = (uint)(capacity * load_factor); - self.table = allocator::new_array(allocator, Entry*, capacity); + self.threshold = (int)(capacity * load_factor); + self.table = alloc::new_array(allocator, Entry*, capacity); return self; } <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashMap* HashMap.tinit(&self, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init(tmem, capacity, load_factor) @inline; } @@ -70,11 +70,11 @@ fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float @param [&inout] allocator : "The allocator to use" @require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); $for var $i = 0; $i < $vacount; $i += 2: @@ -86,11 +86,11 @@ macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uin <* @require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro HashMap* HashMap.tinit_with_key_values(&self, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor); } @@ -101,17 +101,17 @@ macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT @param [&inout] allocator : "The allocator to use" @require keys.len == values.len : "Both keys and values arrays must be the same length" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { assert(keys.len == values.len); self.init(allocator, capacity, load_factor); - for (usz i = 0; i < keys.len; i++) + foreach (i, key : keys) { - self.set(keys[i], values[i]); + self.set(key, values[i]); } return self; } @@ -122,11 +122,11 @@ fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] @param [in] values : "The values for the HashMap entries" @require keys.len == values.len : "Both keys and values arrays must be the same length" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor); } @@ -168,7 +168,7 @@ fn bool HashMap.is_empty(&map) @inline return !map.count; } -fn usz HashMap.len(&map) @inline +fn sz HashMap.len(&map) @inline { return map.count; } @@ -227,7 +227,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr) return val; } uint hash = rehash(key.hash()); - uint index = index_for(hash, map.table.len); + sz index = index_for(hash, map.table.len); for (Entry *e = map.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) return e.value; @@ -260,7 +260,7 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=) break; } uint hash = rehash(key.hash()); - uint index = index_for(hash, map.table.len); + int index = index_for(hash, map.table.len); for (Entry *e = map.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) @@ -315,8 +315,8 @@ fn Key[] HashMap.keys(&self, Allocator allocator) { if (!self.count) return {}; - Key[] list = allocator::alloc_array(allocator, Key, self.count); - usz index = 0; + Key[] list = alloc::alloc_array(allocator, Key, self.count); + int index = 0; foreach (Entry* entry : self.table) { while (entry) @@ -358,8 +358,8 @@ fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline; fn Value[] HashMap.values(&self, Allocator allocator) { if (!self.count) return {}; - Value[] list = allocator::alloc_array(allocator, Value, self.count); - usz index = 0; + Value[] list = alloc::alloc_array(allocator, Value, self.count); + int index; foreach (Entry* entry : self.table) { while (entry) @@ -407,7 +407,7 @@ fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bu $if COPY_KEYS: key = key.copy(map.allocator); $endif - Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); + Entry* entry = alloc::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); map.table[bucket_index] = entry; if (map.count++ >= map.threshold) { @@ -415,26 +415,25 @@ fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bu } } -fn void hashmap_resize(HashMap* map, uint new_capacity) @private +fn void hashmap_resize(HashMap* map, int new_capacity) @private { Entry*[] old_table = map.table; - uint old_capacity = old_table.len; + int old_capacity = (int)old_table.len; if (old_capacity == MAXIMUM_CAPACITY) { - map.threshold = uint.max; + map.threshold = MAXIMUM_CAPACITY; return; } - Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity); + Entry*[] new_table = alloc::new_array(map.allocator, Entry*, new_capacity); hashmap_transfer(map, new_table); map.table = new_table; hashmap_free_internal(map, old_table.ptr); - map.threshold = (uint)(new_capacity * map.load_factor); + map.threshold = (int)(new_capacity * map.load_factor); } -fn usz? HashMap.to_format(&self, Formatter* f) @dynamic +fn sz? HashMap.to_format(&self, Formatter* f) @dynamic { - usz len; - len += f.print("{ ")!; + sz len = f.print("{ ")!; self.@each_entry(; Entry* entry) { if (len > 2) len += f.print(", ")!; @@ -443,17 +442,20 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic return len + f.print(" }"); } +<* + @require new_table.len < int::max : "Too many entries" +*> fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private { Entry*[] src = map.table; - uint new_capacity = new_table.len; - foreach (uint j, Entry *e : src) + int new_capacity = (int)new_table.len; + foreach (Entry *e : src) { if (!e) continue; do { Entry* next = e.next; - uint i = index_for(e.hash, new_capacity); + int i = index_for(e.hash, new_capacity); e.next = new_table[i]; new_table[i] = e; e = next; @@ -478,7 +480,7 @@ fn void hashmap_put_all_for_create(HashMap* map, HashMap* other_map) @private fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private { uint hash = rehash(key.hash()); - uint i = index_for(hash, map.table.len); + int i = index_for(hash, map.table.len); for (Entry *e = map.table[i]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) @@ -492,14 +494,14 @@ fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private fn void hashmap_free_internal(HashMap* map, void* ptr) @inline @private { - allocator::free(map.allocator, ptr); + alloc::free(map.allocator, ptr); } fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private { if (!map.count) return false; uint hash = rehash(key.hash()); - uint i = index_for(hash, map.table.len); + int i = index_for(hash, map.table.len); Entry* prev = map.table[i]; Entry* e = prev; while (e) @@ -525,13 +527,13 @@ fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private return false; } -fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private +fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private { Entry *e = map.table[bucket_index]; $if COPY_KEYS: key = key.copy(map.allocator); $endif - Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); + Entry* entry = alloc::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); map.table[bucket_index] = entry; map.count++; } @@ -539,7 +541,7 @@ fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int fn void hashmap_free_entry(HashMap* map, Entry *entry) @local { $if COPY_KEYS: - allocator::free(map.allocator, entry.key); + alloc::free(map.allocator, entry.key); $endif hashmap_free_internal(map, entry); } @@ -560,7 +562,7 @@ typedef HashMapKeyIterator = HashMapIterator; <* @require idx < self.map.count *> -fn Entry HashMapIterator.get(&self, usz idx) @operator([]) +fn Entry HashMapIterator.get(&self, sz idx) @operator([]) { if (idx < self.index) { @@ -582,19 +584,19 @@ fn Entry HashMapIterator.get(&self, usz idx) @operator([]) return *self.current_entry; } -fn Value HashMapValueIterator.get(&self, usz idx) @operator([]) +fn Value HashMapValueIterator.get(&self, sz idx) @operator([]) { return ((HashMapIterator*)self).get(idx).value; } -fn Key HashMapKeyIterator.get(&self, usz idx) @operator([]) +fn Key HashMapKeyIterator.get(&self, sz idx) @operator([]) { return ((HashMapIterator*)self).get(idx).key; } -fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count; -fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count; -fn usz HashMapIterator.len(self) @operator(len) => self.map.count; +fn sz HashMapValueIterator.len(self) @operator(len) => self.map.count; +fn sz HashMapKeyIterator.len(self) @operator(len) => self.map.count; +fn sz HashMapIterator.len(self) @operator(len) => self.map.count; fn uint rehash(uint hash) @inline @private { @@ -602,9 +604,9 @@ fn uint rehash(uint hash) @inline @private return hash ^ ((hash >> 7) ^ (hash >> 4)); } -macro uint index_for(uint hash, uint capacity) @private +macro int index_for(uint hash, sz capacity) @private { - return hash & (capacity - 1); + return (int)(hash & ((uint)capacity - 1u)); } int dummy @local; diff --git a/test/stdlib/src/collections/hashset.c3 b/test/stdlib/src/collections/hashset.c3 index 8575351..f368d6a 100644 --- a/test/stdlib/src/collections/hashset.c3 +++ b/test/stdlib/src/collections/hashset.c3 @@ -5,8 +5,8 @@ module std::collections::set ; import std::math; import std::io @norecurse; -const uint DEFAULT_INITIAL_CAPACITY = 16; -const uint MAXIMUM_CAPACITY = 1u << 31; +const int DEFAULT_INITIAL_CAPACITY = 16; +const int MAXIMUM_CAPACITY = 1 << 30; const float DEFAULT_LOAD_FACTOR = 0.75; const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy; @@ -26,38 +26,38 @@ struct HashSet (Printable) Entry*[] table; Allocator allocator; <* Number of elements *> - usz count; + int count; <* Resize limit *> - usz threshold; + int threshold; float load_factor; } -fn usz HashSet.len(&self) @operator(len) => self.count; +fn sz HashSet.len(&self) @operator(len) => self.count; <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashSet* HashSet.init(&self, Allocator allocator, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { capacity = math::next_power_of_2(capacity); self.allocator = allocator; - self.threshold = (usz) (capacity * load_factor); + self.threshold = (int)(capacity * load_factor); self.load_factor = load_factor; - self.table = allocator::new_array(allocator, Entry*, capacity); + self.table = alloc::new_array(allocator, Entry*, capacity); return self; } <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashSet* HashSet.tinit(&self, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init(tmem, capacity, load_factor) @inline; } @@ -65,11 +65,11 @@ fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); $for var $i = 0; $i < $vacount; $i++: @@ -80,11 +80,11 @@ macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint ca <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro HashSet* HashSet.tinit_with_values(&self, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor); } @@ -93,11 +93,11 @@ macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INI @param [in] values : "The values for the HashSet" @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); foreach (v : values) self.add(v); @@ -107,11 +107,11 @@ fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, <* @param [in] values : "The values for the HashSet entries" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn HashSet* HashSet.tinit_from_values(&self, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_from_values(tmem, values, capacity, load_factor); } @@ -166,9 +166,9 @@ fn bool HashSet.is_empty(&set) @inline @return "The number of new elements added" @ensure total <= list.len *> -fn usz HashSet.add_all(&set, Value[] list) +fn sz HashSet.add_all(&set, Value[] list) { - usz total; + sz total; foreach (v : list) { if (set.add(v)) total++; @@ -181,9 +181,9 @@ fn usz HashSet.add_all(&set, Value[] list) @return "The number of new elements added" @ensure return <= other.count *> -fn usz HashSet.add_all_from(&set, HashSet* other) +fn sz HashSet.add_all_from(&set, HashSet* other) { - usz total; + sz total; other.@each(;Value value) { if (set.add(value)) total++; @@ -208,7 +208,7 @@ fn bool HashSet.add(&set, Value value) break; } uint hash = rehash(value.hash()); - uint index = index_for(hash, set.table.len); + int index = index_for(hash, set.table.len); for (Entry *e = set.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(value, e.value)) return false; @@ -261,9 +261,9 @@ fn void? HashSet.remove(&set, Value value) @maydiscard if (!hashset_remove_entry_for_value(set, value)) return NOT_FOUND~; } -fn usz HashSet.remove_all(&set, Value[] values) +fn sz HashSet.remove_all(&set, Value[] values) { - usz total; + sz total; foreach (v : values) { if (hashset_remove_entry_for_value(set, v)) total++; @@ -274,9 +274,9 @@ fn usz HashSet.remove_all(&set, Value[] values) <* @param [&in] other : "Other set" *> -fn usz HashSet.remove_all_from(&set, HashSet* other) +fn sz HashSet.remove_all_from(&set, HashSet* other) { - usz total; + sz total; other.@each(;Value val) { if (hashset_remove_entry_for_value(set, val)) total++; @@ -323,7 +323,10 @@ fn void HashSet.clear(&set) set.count = 0; } -fn void HashSet.reserve(&set, usz capacity) +<* + @require capacity > 0 : "Expected positive capacity" +*> +fn void HashSet.reserve(&set, int capacity) { if (capacity > set.threshold) { @@ -336,8 +339,8 @@ fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline; fn Value[] HashSet.values(&self, Allocator allocator) { if (!self.count) return {}; - Value[] list = allocator::alloc_array(allocator, Value, self.count); - usz index = 0; + Value[] list = alloc::alloc_array(allocator, Value, self.count); + sz index; foreach (Entry* entry : self.table) { while (entry) @@ -360,7 +363,7 @@ fn Value[] HashSet.values(&self, Allocator allocator) *> fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other) { - usz new_capacity = math::next_power_of_2(self.count + other.count); + int new_capacity = math::next_power_of_2(self.count + other.count); HashSet result; result.init(allocator, new_capacity, self.load_factor); result.add_all_from(self); @@ -380,7 +383,7 @@ fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, oth fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other) { HashSet result; - result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor); + result.init(allocator, (int)math::min(self.table.len, other.table.len), self.load_factor); // Iterate through the smaller set for efficiency HashSet* smaller = self.count <= other.count ? self : other; @@ -464,9 +467,9 @@ fn bool HashSet.is_subset(&self, HashSet* other) // --- private methods -fn void hashset_add_entry(HashSet* set, uint hash, Value value, uint bucket_index) @private +fn void hashset_add_entry(HashSet* set, uint hash, Value value, int bucket_index) @private { - Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] }); + Entry* entry = alloc::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] }); set.table[bucket_index] = entry; if (set.count++ >= set.threshold) { @@ -474,25 +477,28 @@ fn void hashset_add_entry(HashSet* set, uint hash, Value value, uint bucket_inde } } -fn void hashset_resize(HashSet* self, usz new_capacity) @private +<* + @require new_capacity > 0 : "Expected positive capacity" +*> +fn void hashset_resize(HashSet* self, int new_capacity) @private { Entry*[] old_table = self.table; - usz old_capacity = old_table.len; + int old_capacity = (int)old_table.len; if (old_capacity == MAXIMUM_CAPACITY) { - self.threshold = uint.max; + self.threshold = int::max; return; } - Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity); + Entry*[] new_table = alloc::new_array(self.allocator, Entry*, new_capacity); hashset_transfer(self, new_table); self.table = new_table; hashset_free_internal(self, old_table.ptr); - self.threshold = (uint)(new_capacity * self.load_factor); + self.threshold = (int)(new_capacity * self.load_factor); } -fn usz? HashSet.to_format(&self, Formatter* f) @dynamic +fn sz? HashSet.to_format(&self, Formatter* f) @dynamic { - usz len; + sz len; len += f.print("{ ")!; self.@each(; Value value) { @@ -505,14 +511,14 @@ fn usz? HashSet.to_format(&self, Formatter* f) @dynamic fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private { Entry*[] src = self.table; - uint new_capacity = new_table.len; - foreach (uint j, Entry *e : src) + int new_capacity = (int)new_table.len; + foreach (Entry *e : src) { if (!e) continue; do { Entry* next = e.next; - uint i = index_for(e.hash, new_capacity); + int i = index_for(e.hash, new_capacity); e.next = new_table[i]; new_table[i] = e; e = next; @@ -551,12 +557,12 @@ fn void hashset_put_for_create(HashSet* set, Value value) @private fn void hashset_free_internal(HashSet* self, void* ptr) @inline @private { - allocator::free(self.allocator, ptr); + alloc::free(self.allocator, ptr); } -fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_index) @private +fn void hashset_create_entry(HashSet* set, uint hash, Value value, uint bucket_index) @private { - Entry* entry = allocator::new(set.allocator, Entry, { + Entry* entry = alloc::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] @@ -602,13 +608,13 @@ fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private fn void hashset_free_entry(HashSet* set, Entry *entry) @private { - allocator::free(set.allocator, entry); + alloc::free(set.allocator, entry); } struct HashSetIterator { HashSet* set; - usz bucket_index; + int bucket_index; Entry* current; } @@ -637,7 +643,7 @@ fn Value? HashSetIterator.next(&self) return NOT_FOUND~; } -fn usz HashSetIterator.len(&self) @operator(len) +fn sz HashSetIterator.len(&self) @operator(len) { return self.set.count; } @@ -649,6 +655,6 @@ fn uint rehash(uint hash) @inline @private return hash ^ ((hash >> 7) ^ (hash >> 4)); } -macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1); +macro int index_for(uint hash, sz capacity) @private => (int)(hash & ((uint)capacity - 1u)); int dummy @local; diff --git a/test/stdlib/src/collections/interfacelist.c3 b/test/stdlib/src/collections/interfacelist.c3 index 79e3d4f..0e267c1 100644 --- a/test/stdlib/src/collections/interfacelist.c3 +++ b/test/stdlib/src/collections/interfacelist.c3 @@ -2,7 +2,7 @@ // Use of self source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. <* - @require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`" + @require Type::kind == INTERFACE || Type::kind == ANY : "The kind of an interfacelist must be an interface or `any`" *> module std::collections::interfacelist ; import std::io,std::math; @@ -23,8 +23,8 @@ alias InterfaceTest = fn bool(Type type, Type context); *> struct InterfaceList (Printable) { - usz size; - usz capacity; + sz size; + sz capacity; Allocator allocator; Type* entries; } @@ -37,14 +37,14 @@ struct InterfaceList (Printable) @param [&inout] allocator : "The allocator to use" @param initial_capacity : "The initial capacity to reserve, defaults to 16" *> -fn InterfaceList* InterfaceList.init(&self, Allocator allocator, usz initial_capacity = 16) +fn InterfaceList* InterfaceList.init(&self, Allocator allocator, sz initial_capacity = 16) { self.allocator = allocator; self.size = 0; if (initial_capacity > 0) { initial_capacity = math::next_power_of_2(initial_capacity); - self.entries = allocator::alloc_array(allocator, Type, initial_capacity); + self.entries = alloc::alloc_array(allocator, Type, initial_capacity); } else { @@ -59,7 +59,7 @@ fn InterfaceList* InterfaceList.init(&self, Allocator allocator, usz initial_cap @param initial_capacity : "The initial capacity to reserve" *> -fn InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16) +fn InterfaceList* InterfaceList.tinit(&self, sz initial_capacity = 16) { return self.init(tmem, initial_capacity) @inline; } @@ -73,7 +73,7 @@ fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null; macro void InterfaceList.push(&self, element) { if (!self.allocator) self.allocator = tmem; - interfacelist_append(self, allocator::clone(self.allocator, element)); + interfacelist_append(self, alloc::clone(self.allocator, element)); } <* @@ -81,7 +81,7 @@ macro void InterfaceList.push(&self, element) *> fn void InterfaceList.free_element(&self, Type element) @inline { - allocator::free(self.allocator, element.ptr); + alloc::free(self.allocator, element.ptr); } <* @@ -95,7 +95,7 @@ fn Type? InterfaceList.copy_pop(&self, Allocator allocator) { if (!self.size) return NO_MORE_ELEMENT~; defer self.free_element(self.entries[self.size]); - return (Type)allocator::clone_any(allocator, self.entries[--self.size]); + return (Type)alloc::clone_any(allocator, self.entries[--self.size]); } <* @@ -123,7 +123,7 @@ fn Type? InterfaceList.pop_retained(&self) *> fn void InterfaceList.clear(&self) { - for (usz i = 0; i < self.size; i++) + for (sz i = 0; i < self.size; i++) { self.free_element(self.entries[i]); } @@ -155,7 +155,7 @@ fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator) if (!self.size) return NO_MORE_ELEMENT~; defer self.free_element(self.entries[self.size]); defer self.remove_at(0); - return (Type)allocator::clone_any(allocator, self.entries[0]); + return (Type)alloc::clone_any(allocator, self.entries[0]); } <* @@ -170,9 +170,9 @@ fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem); Remove the element at the particular index. @param index : "The index of the element to remove" - @require index < self.size + @require index <= self.size && index >= 0 : "The index is out of bounds" *> -fn void InterfaceList.remove_at(&self, usz index) +fn void InterfaceList.remove_at(&self, sz index) { if (!--self.size || index == self.size) return; self.free_element(self.entries[index]); @@ -190,7 +190,7 @@ fn void InterfaceList.add_all(&self, InterfaceList* other_list) self.reserve(other_list.size); foreach (value : other_list) { - self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value); + self.entries[self.size++] = (Type)alloc::clone_any(self.allocator, value); } } @@ -200,9 +200,9 @@ fn void InterfaceList.add_all(&self, InterfaceList* other_list) fn void InterfaceList.reverse(&self) { if (self.size < 2) return; - usz half = self.size / 2U; - usz end = self.size - 1; - for (usz i = 0; i < half; i++) + sz half = self.size / 2U; + sz end = self.size - 1; + for (sz i = 0; i < half; i++) { self.swap(i, end - i); } @@ -234,17 +234,17 @@ macro void InterfaceList.push_front(&self, value) @param index : "the index where the element should be inserted" @param type : "the value to insert" - @require index <= self.size : "The index is out of bounds" + @require index <= self.size && index >= 0 : "The index is out of bounds" @require $defined(Type t = &type) : "Type must implement the interface" *> -macro void InterfaceList.insert_at(&self, usz index, type) +macro void InterfaceList.insert_at(&self, sz index, type) { if (index == self.size) { self.push(type); return; } - Type value = allocator::clone(self.allocator, type); + Type value = alloc::clone(self.allocator, type); self._insert_at(self, index, value); } @@ -305,7 +305,7 @@ fn bool InterfaceList.is_empty(&self) @inline @return "The number of elements in the list" *> -fn usz InterfaceList.len(&self) @operator(len) @inline +fn sz InterfaceList.len(&self) @operator(len) @inline { return self.size; } @@ -316,9 +316,9 @@ fn usz InterfaceList.len(&self) @operator(len) @inline @param index : "The index of the element to retrieve" @return "The element at the index" @return? TYPE_MISMATCH, NO_MORE_ELEMENT - @require index < self.size : "Index out of range" + @require index <= self.size && index >= 0 : "The index is out of bounds" *> -fn Type InterfaceList.get(&self, usz index) @inline @operator([]) +fn Type InterfaceList.get(&self, sz index) @inline @operator([]) { return self.entries[index]; } @@ -330,7 +330,7 @@ fn void InterfaceList.free(&self) { if (!self.allocator) return; self.clear(); - allocator::free(self.allocator, self.entries); + alloc::free(self.allocator, self.entries); self.capacity = 0; self.entries = null; } @@ -340,10 +340,10 @@ fn void InterfaceList.free(&self) @param i : "Index of one of the elements" @param j : "Index of the other element" - @require i < self.size : "The first index is out of range" - @require j < self.size : "The second index is out of range" + @require i <= self.size && i >= 0 : "The first index is out of bounds" + @require j <= self.size && j >= 0 : "The second index is out of bounds" *> -fn void InterfaceList.swap(&self, usz i, usz j) +fn void InterfaceList.swap(&self, sz i, sz j) { Type temp = self.entries[i]; self.entries[i] = self.entries[j]; @@ -353,7 +353,7 @@ fn void InterfaceList.swap(&self, usz i, usz j) <* Print the list to a formatter. *> -fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic +fn sz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic { switch (self.size) { @@ -362,7 +362,7 @@ fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic case 1: return formatter.printf("[%s]", self.entries[0])!; default: - usz n = formatter.print("[")!; + sz n = formatter.print("[")!; foreach (i, element : self.entries[:self.size]) { if (i != 0) formatter.print(", ")!; @@ -379,7 +379,7 @@ fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic @param filter : "The function to determine if it should be removed or not" @return "the number of deleted elements" *> -fn usz InterfaceList.remove_if(&self, InterfacePredicate filter) +fn sz InterfaceList.remove_if(&self, InterfacePredicate filter) { return interfacelist_remove_if(self, filter, false); } @@ -390,7 +390,7 @@ fn usz InterfaceList.remove_if(&self, InterfacePredicate filter) @param selection : "The function to determine if it should be kept or not" @return "the number of deleted elements" *> -fn usz InterfaceList.retain_if(&self, InterfacePredicate selection) +fn sz InterfaceList.retain_if(&self, InterfacePredicate selection) { return interfacelist_remove_if(self, selection, true); } @@ -402,7 +402,7 @@ fn usz InterfaceList.retain_if(&self, InterfacePredicate selection) @param context : "The context to the function" @return "the number of deleted elements" *> -fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context) +fn sz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context) { return interfacelist_remove_using_test(self, filter, false, context); } @@ -414,7 +414,7 @@ fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context @param context : "The context to the function" @return "the number of deleted elements" *> -fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context) +fn sz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context) { return interfacelist_remove_using_test(self, selection, true, context); } @@ -423,14 +423,15 @@ fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type cont Reserve memory so that at least the `min_capacity` exists. @param min_capacity : "The min capacity to hold" + *> -fn void InterfaceList.reserve(&self, usz min_capacity) +fn void InterfaceList.reserve(&self, sz min_capacity) { - if (!min_capacity) return; + if (min_capacity <= 0) return; if (self.capacity >= min_capacity) return; if (!self.allocator) self.allocator = tmem; min_capacity = math::next_power_of_2(min_capacity); - self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity); + self.entries = alloc::realloc(self.allocator, self.entries, Type::size * min_capacity); self.capacity = min_capacity; } @@ -439,10 +440,10 @@ fn void InterfaceList.reserve(&self, usz min_capacity) @param index : "The index where to set the value." @param value : "The value to set" - @require index <= self.size : "Index out of range" + @require index <= self.size || index >= 0 : "Index out of range" @require $defined(Type t = &value) : "Value must implement the interface" *> -macro void InterfaceList.set(&self, usz index, value) +macro void InterfaceList.set(&self, sz index, value) { if (index == self.size) { @@ -450,18 +451,18 @@ macro void InterfaceList.set(&self, usz index, value) return; } self.free_element(self.entries[index]); - self.entries[index] = allocator::clone(self.allocator, value); + self.entries[index] = alloc::clone(self.allocator, value); } // -- private -fn void interfacelist_ensure_capacity(InterfaceList* self, usz added = 1) @inline @private +fn void interfacelist_ensure_capacity(InterfaceList* self, sz added = 1) @inline @private { - usz new_size = self.size + added; + sz new_size = self.size + added; if (self.capacity >= new_size) return; - assert(new_size < usz.max / 2U); - usz new_capacity = self.capacity ? 2U * self.capacity : 16U; + assert(new_size < sz::max / 2U); + sz new_capacity = self.capacity ? 2U * self.capacity : 16U; while (new_capacity < new_size) new_capacity *= 2U; self.reserve(new_capacity); } @@ -473,12 +474,12 @@ fn void interfacelist_append(InterfaceList* self, Type element) @local } <* - @require index < self.size + @require index <= self.size || index >= 0 : "Index out of range" *> -fn void interfacelist_insert_at(InterfaceList* self, usz index, Type value) @local +fn void interfacelist_insert_at(InterfaceList* self, sz index, Type value) @local { interfacelist_ensure_capacity(self); - for (usz i = self.size; i > index; i--) + for (sz i = self.size; i > index; i--) { self.entries[i] = self.entries[i - 1]; } @@ -486,10 +487,10 @@ fn void interfacelist_insert_at(InterfaceList* self, usz index, Type value) @loc self.entries[index] = value; } -macro usz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest filter, bool $invert, ctx) @local +macro sz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest filter, bool $invert, ctx) @local { - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) + sz size = self.size; + for (sz i = size, sz k = size; k > 0; k = i) { // Find last index of item to be deleted. $if $invert: @@ -498,8 +499,8 @@ macro usz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest fil while (i > 0 && filter(self.entries[i - 1], ctx)) i--; $endif // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - for (usz j = i; j < k; j++) self.free_element(self.entries[j]); + sz n = self.size - k; + for (sz j = i; j < k; j++) self.free_element(self.entries[j]); self.entries[i:n] = self.entries[k:n]; self.size -= k - i; // Find last index of item not to be deleted. @@ -512,10 +513,10 @@ macro usz interfacelist_remove_using_test(InterfaceList* self, InterfaceTest fil return size - self.size; } -macro usz interfacelist_remove_if(InterfaceList* self, InterfacePredicate filter, bool $invert) @local +macro sz interfacelist_remove_if(InterfaceList* self, InterfacePredicate filter, bool $invert) @local { - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) + sz size = self.size; + for (sz i = size, sz k = size; k > 0; k = i) { // Find last index of item to be deleted. $if $invert: @@ -524,8 +525,8 @@ macro usz interfacelist_remove_if(InterfaceList* self, InterfacePredicate filter while (i > 0 && filter(self.entries[i - 1])) i--; $endif // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - for (usz j = i; j < k; j++) self.free_element(self.entries[j]); + sz n = self.size - k; + for (sz j = i; j < k; j++) self.free_element(self.entries[j]); self.entries[i:n] = self.entries[k:n]; self.size -= k - i; // Find last index of item not to be deleted. diff --git a/test/stdlib/src/collections/linked_blockingqueue.c3 b/test/stdlib/src/collections/linked_blockingqueue.c3 index 4b9a5e6..0d090b6 100644 --- a/test/stdlib/src/collections/linked_blockingqueue.c3 +++ b/test/stdlib/src/collections/linked_blockingqueue.c3 @@ -20,9 +20,9 @@ struct LinkedBlockingQueue <* Last element in queue *> QueueEntry* tail; <* Current number of elements *> - usz count; + sz count; <* Maximum capacity (0 for unbounded) *> - usz capacity; + sz capacity; Mutex lock; ConditionVariable not_empty; ConditionVariable not_full; @@ -34,7 +34,7 @@ struct LinkedBlockingQueue @param capacity : "Maximum capacity (0 for unbounded)" @require !self.is_initialized() : "Queue was already initialized" *> -fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0) +fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, sz capacity = 0) { self.allocator = allocator; self.capacity = capacity; @@ -48,7 +48,7 @@ fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz return self; } -fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0) +fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, sz capacity = 0) { return self.init(tmem, capacity) @inline; } @@ -65,7 +65,7 @@ fn void LinkedBlockingQueue.free(&self) while (entry != null) { QueueEntry* next = entry.next; - allocator::free(self.allocator, entry); + alloc::free(self.allocator, entry); entry = next; } }; @@ -129,7 +129,7 @@ fn void LinkedBlockingQueue.push(&self, Value value) self.not_full.wait(&self.lock); } - QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { + QueueEntry* entry = alloc::new(self.allocator, QueueEntry, { .value = value, .next = null, .prev = null @@ -158,7 +158,7 @@ fn Value LinkedBlockingQueue.poll(&self) QueueEntry* entry = linkedblockingqueue_unlink_head(self); Value value = entry.value; - allocator::free(self.allocator, entry); + alloc::free(self.allocator, entry); if (self.capacity > 0) { self.not_full.signal(); @@ -182,7 +182,7 @@ fn Value? LinkedBlockingQueue.pop(&self) QueueEntry* entry = linkedblockingqueue_unlink_head(self); Value value = entry.value; - allocator::free(self.allocator, entry); + alloc::free(self.allocator, entry); if (self.capacity > 0) { @@ -219,7 +219,7 @@ fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout) QueueEntry* entry = linkedblockingqueue_unlink_head(self); Value value = entry.value; - allocator::free(self.allocator, entry); + alloc::free(self.allocator, entry); // Must signal not_full after removing an item if (self.capacity > 0) @@ -235,7 +235,7 @@ fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout) @require self.is_initialized() : "Queue must be initialized" @return "Current size of the queue" *> -fn usz LinkedBlockingQueue.size(&self) +fn sz LinkedBlockingQueue.size(&self) { self.lock.@in_lock() { @@ -268,7 +268,7 @@ fn void? LinkedBlockingQueue.try_push(&self, Value value) { if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~; - QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { + QueueEntry* entry = alloc::new(self.allocator, QueueEntry, { .value = value, .next = null, .prev = null @@ -302,7 +302,7 @@ fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout) if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~; } - QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { + QueueEntry* entry = alloc::new(self.allocator, QueueEntry, { .value = value, .next = null, .prev = null diff --git a/test/stdlib/src/collections/linked_hashmap.c3 b/test/stdlib/src/collections/linked_hashmap.c3 index 3540ffd..30eaab3 100644 --- a/test/stdlib/src/collections/linked_hashmap.c3 +++ b/test/stdlib/src/collections/linked_hashmap.c3 @@ -27,8 +27,8 @@ struct LinkedHashMap (Printable) { LinkedEntry*[] table; Allocator allocator; - usz count; - usz threshold; + int count; + int threshold; float load_factor; <* First inserted LinkedEntry *> LinkedEntry* head; @@ -40,17 +40,17 @@ struct LinkedHashMap (Printable) <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { capacity = math::next_power_of_2(capacity); self.allocator = allocator; self.load_factor = load_factor; - self.threshold = (usz)(capacity * load_factor); - self.table = allocator::new_array(allocator, LinkedEntry*, capacity); + self.threshold = (int)(capacity * load_factor); + self.table = alloc::new_array(allocator, LinkedEntry*, capacity); self.head = null; self.tail = null; return self; @@ -58,11 +58,11 @@ fn LinkedHashMap* LinkedHashMap.init(&self, Allocator allocator, usz capacity = <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashMap* LinkedHashMap.tinit(&self, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init(tmem, capacity, load_factor) @inline; } @@ -71,11 +71,11 @@ fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPA @param [&inout] allocator : "The allocator to use" @require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); $for var $i = 0; $i < $vacount; $i += 2: @@ -87,11 +87,11 @@ macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocat <* @require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor); } @@ -102,17 +102,15 @@ macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capaci @param [&inout] allocator : "The allocator to use" @require keys.len == values.len : "Both keys and values arrays must be the same length" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" - @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { - assert(keys.len == values.len); self.init(allocator, capacity, load_factor); - for (usz i = 0; i < keys.len; i++) + foreach (i, key : keys) { - self.set(keys[i], values[i]); + self.set(key, values[i]); } return self; } @@ -122,11 +120,11 @@ fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator alloc @param [in] values : "The values for the LinkedHashMap entries" @require keys.len == values.len : "Both keys and values arrays must be the same length" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Map was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor); } @@ -168,7 +166,7 @@ fn bool LinkedHashMap.is_empty(&map) @inline return !map.count; } -fn usz LinkedHashMap.len(&map) @inline => map.count; +fn sz LinkedHashMap.len(&map) @inline => map.count; fn Value*? LinkedHashMap.get_ref(&map, Key key) { @@ -206,7 +204,7 @@ macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr) return val; } uint hash = rehash(key.hash()); - uint index = index_for(hash, map.table.len); + int index = index_for(hash, map.table.len); for (LinkedEntry *e = map.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) return e.value; @@ -233,7 +231,7 @@ fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=) break; } uint hash = rehash(key.hash()); - uint index = index_for(hash, map.table.len); + int index = index_for(hash, map.table.len); for (LinkedEntry *e = map.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) @@ -290,8 +288,8 @@ fn Key[] LinkedHashMap.keys(&self, Allocator allocator) { if (!self.count) return {}; - Key[] list = allocator::alloc_array(allocator, Key, self.count); - usz index = 0; + Key[] list = alloc::alloc_array(allocator, Key, self.count); + int index = 0; LinkedEntry* entry = self.head; while (entry) @@ -329,8 +327,8 @@ fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline; fn Value[] LinkedHashMap.values(&self, Allocator allocator) { if (!self.count) return {}; - Value[] list = allocator::alloc_array(allocator, Value, self.count); - usz index = 0; + Value[] list = alloc::alloc_array(allocator, Value, self.count); + int index = 0; LinkedEntry* entry = self.head; while (entry) { @@ -396,13 +394,13 @@ fn bool LinkedHashMapIterator.has_next(&self) // --- private methods -fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, uint bucket_index) @private +fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, int bucket_index) @private { $if COPY_KEYS: key = key.copy(map.allocator); $endif - LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { + LinkedEntry* entry = alloc::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, @@ -432,23 +430,23 @@ fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value va } } -fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private +fn void linkedhashmap_resize(LinkedHashMap* map, int new_capacity) @private { LinkedEntry*[] old_table = map.table; - uint old_capacity = old_table.len; + int old_capacity = old_table.len; if (old_capacity == MAXIMUM_CAPACITY) { - map.threshold = uint.max; + map.threshold = int::max; return; } - LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity); + LinkedEntry*[] new_table = alloc::new_array(map.allocator, LinkedEntry*, new_capacity); map.table = new_table; - map.threshold = (uint)(new_capacity * map.load_factor); + map.threshold = (int)(new_capacity * map.load_factor); // Rehash all entries - linked list order remains unchanged - foreach (uint i, LinkedEntry *e : old_table) + foreach (i, LinkedEntry *e : old_table) { if (!e) continue; @@ -461,7 +459,7 @@ fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private do { LinkedEntry* next = e.next; - if ((e.hash & old_capacity) == 0) + if ((e.hash & (uint)old_capacity) == 0) { if (!lo_tail) { @@ -505,9 +503,9 @@ fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private linkedhashmap_free_internal(map, old_table.ptr); } -fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic +fn sz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic { - usz len; + sz len; len += f.print("{ ")!; self.@each_entry(; LinkedEntry* entry) { @@ -520,14 +518,14 @@ fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @private { LinkedEntry*[] src = map.table; - uint new_capacity = new_table.len; - foreach (uint j, LinkedEntry *e : src) + int new_capacity = (int)new_table.len; + foreach (LinkedEntry *e : src) { if (!e) continue; do { LinkedEntry* next = e.next; - uint i = index_for(e.hash, new_capacity); + int i = index_for(e.hash, new_capacity); e.next = new_table[i]; new_table[i] = e; e = next; @@ -547,7 +545,7 @@ fn void linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* othe fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @private { uint hash = rehash(key.hash()); - uint i = index_for(hash, map.table.len); + int i = index_for(hash, map.table.len); for (LinkedEntry *e = map.table[i]; e != null; e = e.next) { if (e.hash == hash && equals(key, e.key)) @@ -561,7 +559,7 @@ fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @ fn void linkedhashmap_free_internal(LinkedHashMap* map, void* ptr) @inline @private { - allocator::free(map.allocator, ptr); + alloc::free(map.allocator, ptr); } fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private @@ -569,7 +567,7 @@ fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private if (!map.count) return false; uint hash = rehash(key.hash()); - uint i = index_for(hash, map.table.len); + int i = index_for(hash, map.table.len); LinkedEntry* prev = null; LinkedEntry* e = map.table[i]; @@ -620,7 +618,7 @@ fn void linkedhashmap_create_entry(LinkedHashMap* map, uint hash, Key key, Value $if COPY_KEYS: key = key.copy(map.allocator); $endif - LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); + LinkedEntry* entry = alloc::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); map.table[bucket_index] = entry; map.count++; } @@ -628,7 +626,7 @@ fn void linkedhashmap_create_entry(LinkedHashMap* map, uint hash, Key key, Value fn void linkedhashmap_free_entry(LinkedHashMap* self, LinkedEntry *entry) @local { $if COPY_KEYS: - allocator::free(self.allocator, entry.key); + alloc::free(self.allocator, entry.key); $endif linkedhashmap_free_internal(self, entry); } @@ -644,8 +642,8 @@ struct LinkedHashMapIterator typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator; typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator; -fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count; -fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count; -fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count; +fn sz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count; +fn sz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count; +fn sz LinkedHashMapIterator.len(self) @operator(len) => self.map.count; int dummy @local; diff --git a/test/stdlib/src/collections/linked_hashset.c3 b/test/stdlib/src/collections/linked_hashset.c3 index 77e89d4..62831e2 100644 --- a/test/stdlib/src/collections/linked_hashset.c3 +++ b/test/stdlib/src/collections/linked_hashset.c3 @@ -24,32 +24,31 @@ struct LinkedHashSet (Printable) LinkedEntry*[] table; Allocator allocator; <* Number of elements *> - usz count; + int count; <* Resize limit *> - usz threshold; + int threshold; float load_factor; - <* Resize limit *> LinkedEntry* head; <* First inserted LinkedEntry *> LinkedEntry* tail; } -fn usz LinkedHashSet.len(&self) @operator(len) => self.count; +fn sz LinkedHashSet.len(&self) @operator(len) => self.count; <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { capacity = math::next_power_of_2(capacity); self.allocator = allocator; - self.threshold = (usz)(capacity * load_factor); + self.threshold = (int)(capacity * load_factor); self.load_factor = load_factor; - self.table = allocator::new_array(allocator, LinkedEntry*, capacity); + self.table = alloc::new_array(allocator, LinkedEntry*, capacity); self.head = null; self.tail = null; @@ -58,11 +57,11 @@ fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity = <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashSet* LinkedHashSet.tinit(&self, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init(tmem, capacity, load_factor) @inline; } @@ -70,11 +69,11 @@ fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPA <* @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); $for var $i = 0; $i < $vacount; $i++: @@ -85,11 +84,11 @@ macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, <* @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" - @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" + @require capacity <= MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor); } @@ -98,11 +97,11 @@ macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = @param [in] values : "The values for the LinkedHashSet" @param [&inout] allocator : "The allocator to use" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { self.init(allocator, capacity, load_factor); foreach (v : values) self.add(v); @@ -112,11 +111,11 @@ fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Val <* @param [in] values : "The values for the LinkedHashSet entries" @require capacity > 0 : "The capacity must be 1 or higher" - @require load_factor > 0.0 : "The load factor must be higher than 0" + @require load_factor > 0.0 && load_factor <= 1 : "The load factor must be higher than 0 and less or equal to 1" @require !self.is_initialized() : "Set was already initialized" @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" *> -fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) +fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, sz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) { return self.init_from_values(tmem, values, capacity, load_factor); } @@ -176,9 +175,9 @@ fn bool LinkedHashSet.is_empty(&set) @inline @return "The number of new elements added" @ensure total <= list.len *> -fn usz LinkedHashSet.add_all(&set, Value[] list) +fn int LinkedHashSet.add_all(&set, Value[] list) { - usz total; + int total; foreach (v : list) { if (set.add(v)) total++; @@ -191,9 +190,9 @@ fn usz LinkedHashSet.add_all(&set, Value[] list) @return "The number of new elements added" @ensure return <= other.count *> -fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other) +fn int LinkedHashSet.add_all_from(&set, LinkedHashSet* other) { - usz total; + int total; other.@each(;Value value) { if (set.add(value)) total++; @@ -218,7 +217,7 @@ fn bool LinkedHashSet.add(&set, Value value) break; } uint hash = rehash(value.hash()); - uint index = index_for(hash, set.table.len); + int index = index_for(hash, set.table.len); for (LinkedEntry *e = set.table[index]; e != null; e = e.next) { if (e.hash == hash && equals(value, e.value)) return false; @@ -269,9 +268,9 @@ fn void? LinkedHashSet.remove(&set, Value value) @maydiscard if (!linkedhashset_remove_entry_for_value(set, value)) return NOT_FOUND~; } -fn usz LinkedHashSet.remove_all(&set, Value[] values) +fn int LinkedHashSet.remove_all(&set, Value[] values) { - usz total; + int total; foreach (v : values) { if (linkedhashset_remove_entry_for_value(set, v)) total++; @@ -282,9 +281,9 @@ fn usz LinkedHashSet.remove_all(&set, Value[] values) <* @param [&in] other : "Other set" *> -fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other) +fn int LinkedHashSet.remove_all_from(&set, LinkedHashSet* other) { - usz total; + int total; other.@each(;Value val) { if (linkedhashset_remove_entry_for_value(set, val)) total++; @@ -330,7 +329,7 @@ fn void LinkedHashSet.clear(&set) set.tail = null; } -fn void LinkedHashSet.reserve(&set, usz capacity) +fn void LinkedHashSet.reserve(&set, int capacity) { if (capacity > set.threshold) { @@ -349,7 +348,7 @@ fn void LinkedHashSet.reserve(&set, usz capacity) *> fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other) { - usz new_capacity = math::next_power_of_2(self.count + other.count); + int new_capacity = math::next_power_of_2(self.count + other.count); LinkedHashSet result; result.init(allocator, new_capacity, self.load_factor); result.add_all_from(self); @@ -369,7 +368,7 @@ fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.s fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other) { LinkedHashSet result; - result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor); + result.init(allocator, (int)math::min(self.table.len, other.table.len), self.load_factor); // Iterate through the smaller set for efficiency LinkedHashSet* smaller = self.count <= other.count ? self : other; @@ -451,9 +450,9 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other) // --- private methods -fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint bucket_index) @private +fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private { - LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, { + LinkedEntry* entry = alloc::new(set.allocator, LinkedEntry, { .hash = hash, .value = value, .next = set.table[bucket_index], @@ -482,23 +481,23 @@ fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint } } -fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private +fn void linkedhashset_resize(LinkedHashSet* set, int new_capacity) @private { LinkedEntry*[] old_table = set.table; - usz old_capacity = old_table.len; + int old_capacity = old_table.len; - if (old_capacity == MAXIMUM_CAPACITY) + if (old_capacity >= int::max / 2) { - set.threshold = uint.max; + set.threshold = int::max; return; } - LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity); + LinkedEntry*[] new_table = alloc::new_array(set.allocator, LinkedEntry*, new_capacity); set.table = new_table; - set.threshold = (uint)(new_capacity * set.load_factor); + set.threshold = (int)(new_capacity * set.load_factor); // Rehash all entries - linked list order remains unchanged - foreach (uint i, LinkedEntry *e : old_table) + foreach (int i, LinkedEntry *e : old_table) { if (!e) continue; @@ -555,9 +554,9 @@ fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private linkedhashset_free_internal(set, old_table.ptr); } -fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic +fn sz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic { - usz len; + sz len; len += f.print("{ ")!; self.@each(; Value value) { @@ -570,14 +569,14 @@ fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @private { LinkedEntry*[] src = set.table; - uint new_capacity = new_table.len; - foreach (uint j, LinkedEntry *e : src) + int new_capacity = (int)new_table.len; + foreach (LinkedEntry *e : src) { if (!e) continue; do { LinkedEntry* next = e.next; - uint i = index_for(e.hash, new_capacity); + int i = index_for(e.hash, new_capacity); e.next = new_table[i]; new_table[i] = e; e = next; @@ -589,7 +588,7 @@ fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @p fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private { uint hash = rehash(value.hash()); - uint i = index_for(hash, set.table.len); + int i = index_for(hash, set.table.len); for (LinkedEntry *e = set.table[i]; e != null; e = e.next) { if (e.hash == hash && equals(value, e.value)) @@ -603,12 +602,12 @@ fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private fn void linkedhashset_free_internal(LinkedHashSet* set, void* ptr) @inline @private { - allocator::free(set.allocator, ptr); + alloc::free(set.allocator, ptr); } fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private { - LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, { + LinkedEntry* entry = alloc::new(set.allocator, LinkedEntry, { .hash = hash, .value = value, .next = set.table[bucket_index], @@ -638,7 +637,7 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p if (!set.count) return false; uint hash = rehash(value.hash()); - uint i = index_for(hash, set.table.len); + int i = index_for(hash, set.table.len); LinkedEntry* prev = null; LinkedEntry* e = set.table[i]; @@ -685,7 +684,7 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p fn void linkedhashset_free_entry(LinkedHashSet* set, LinkedEntry *entry) @private { - allocator::free(set.allocator, entry); + alloc::free(set.allocator, entry); } struct LinkedHashSetIterator @@ -722,7 +721,7 @@ fn bool LinkedHashSetIterator.has_next(&self) return self.current && self.current.after != null; } -fn usz LinkedHashSetIterator.len(&self) @operator(len) +fn sz LinkedHashSetIterator.len(&self) @operator(len) { return self.set.count; } diff --git a/test/stdlib/src/collections/linkedlist.c3 b/test/stdlib/src/collections/linkedlist.c3 index 58a3207..a09cb6d 100644 --- a/test/stdlib/src/collections/linkedlist.c3 +++ b/test/stdlib/src/collections/linkedlist.c3 @@ -16,14 +16,14 @@ struct Node struct LinkedList { Allocator allocator; - usz size; + sz size; Node* _first; Node* _last; } -fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic +fn sz? LinkedList.to_format(&self, Formatter* f) @dynamic { - usz len = f.print("{ ")!; + sz len = f.print("{ ")!; for (Node* node = self._first; node != null; node = node.next) { len += f.printf(node.next ? "%s, " : "%s", node.value)!; @@ -45,7 +45,7 @@ macro LinkedList @tnew(Type[] #default_values = {}) } <* - @param [&inout] allocator : "The allocator to use, defaults to the heap allocator" + @param [&inout] allocator : "The allocator to use" @return "the initialized list" *> fn LinkedList* LinkedList.init(&self, Allocator allocator) @@ -66,13 +66,13 @@ fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null; *> macro void LinkedList.free_node(&self, Node* node) @private { - allocator::free(self.allocator, node); + alloc::free(self.allocator, node); } macro Node* LinkedList.alloc_node(&self) @private { if (!self.allocator) self.allocator = tmem; - return allocator::alloc(self.allocator, Node); + return alloc::alloc(self.allocator, Node); } fn void LinkedList.push_front(&self, Type value) @@ -149,12 +149,12 @@ fn void LinkedList.clear(&self) self.size = 0; } -fn usz LinkedList.len(&self) @inline => self.size; +fn sz LinkedList.len(&self) @inline => self.size; <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -macro Node* LinkedList.node_at_index(&self, usz index) +macro Node* LinkedList.node_at_index(&self, sz index) { if (index * 2 >= self.size) { @@ -169,41 +169,41 @@ macro Node* LinkedList.node_at_index(&self, usz index) } <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn Type LinkedList.get(&self, usz index) +fn Type LinkedList.get(&self, sz index) { return self.node_at_index(index).value; } <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn Type* LinkedList.get_ref(&self, usz index) +fn Type* LinkedList.get_ref(&self, sz index) { return &self.node_at_index(index).value; } <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn void LinkedList.set(&self, usz index, Type element) +fn void LinkedList.set(&self, sz index, Type element) { self.node_at_index(index).value = element; } -fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE) +fn sz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE) { - for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i) + for (Node* node = self._first, sz i = 0; node != null; node = node.next, ++i) { if (node.value == t) return i; } return NOT_FOUND~; } -fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE) +fn sz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE) { - for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i) + for (Node* node = self._last, sz i = self.size - 1; node != null; node = node.prev, --i) { if (node.value == t) return i; if (i == 0) break; @@ -213,17 +213,17 @@ fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE) <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn void LinkedList.remove_at(&self, usz index) +fn void LinkedList.remove_at(&self, sz index) { linked_list_unlink(self, self.node_at_index(index)); } <* - @require index <= self.size + @require index <= self.size && index >= 0 : "Index out of bounds" *> -fn void LinkedList.insert_at(&self, usz index, Type element) +fn void LinkedList.insert_at(&self, sz index, Type element) { switch (index) { @@ -275,9 +275,9 @@ fn void linked_list_unlink_first(LinkedList* l) @private l.size--; } -fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE) +fn sz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE) { - usz start = self.size; + sz start = self.size; Node* node = self._first; while (node) { @@ -306,7 +306,7 @@ fn bool LinkedList.is_empty(&self) return !self._first; } -fn Type? LinkedList.pop_front(&self) +fn Type? LinkedList.pop_first(&self) { if (!self._first) return NO_MORE_ELEMENT~; defer linked_list_unlink_first(self); @@ -424,23 +424,23 @@ struct LinkedListArrayView { LinkedList* list; Node* current_node; - usz current_index; + sz current_index; } -fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size; +fn sz LinkedListArrayView.len(&self) @operator(len) => self.list.size; <* - @require index < self.list.size + @require index < self.list.size && index >= 0 : "Index out of bounds" *> -fn Type LinkedListArrayView.get(&self, usz index) @operator([]) +fn Type LinkedListArrayView.get(&self, sz index) @operator([]) { return *self.get_ref(index); } <* - @require index < self.list.size + @require index < self.list.size && index >= 0 : "Index out of bounds" *> -fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[]) +fn Type* LinkedListArrayView.get_ref(&self, sz index) @operator(&[]) { if (index == self.list.size - 1) { diff --git a/test/stdlib/src/collections/list.c3 b/test/stdlib/src/collections/list.c3 index 8a9a7c9..5d98fc1 100644 --- a/test/stdlib/src/collections/list.c3 +++ b/test/stdlib/src/collections/list.c3 @@ -7,27 +7,27 @@ import std::io, std::math, std::collections::list_common; alias ElementPredicate = fn bool(Type *type); alias ElementTest = fn bool(Type *type, any context); const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); -const ELEMENT_IS_POINTER = Type.kindof == POINTER; +const ELEMENT_IS_POINTER = Type::kind == POINTER; const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy; const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR }; -macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT; +macro bool type_is_overaligned() => Type::alignment > mem::DEFAULT_MEM_ALIGNMENT; struct List (Printable) { - usz size; - usz capacity; + sz size; + sz capacity; Allocator allocator; Type *entries; } <* @param initial_capacity : "The initial capacity to reserve" - @param [&inout] allocator : "The allocator to use, defaults to the heap allocator" + @param [&inout] allocator : "The allocator to use" *> -fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16) +fn List* List.init(&self, Allocator allocator, sz initial_capacity = 16) { self.allocator = allocator; self.size = 0; @@ -43,7 +43,7 @@ fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16) @param initial_capacity : "The initial capacity to reserve" *> -fn List* List.tinit(&self, usz initial_capacity = 16) +fn List* List.tinit(&self, sz initial_capacity = 16) { return self.init(tmem, initial_capacity) @inline; } @@ -87,7 +87,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types) fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy; -fn usz? List.to_format(&self, Formatter* formatter) @dynamic +fn sz? List.to_format(&self, Formatter* formatter) @dynamic { switch (self.size) { @@ -96,7 +96,7 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic case 1: return formatter.printf("[%s]", self.entries[0])!; default: - usz n = formatter.print("[")!; + sz n = formatter.print("[")!; foreach (i, element : self.entries[:self.size]) { if (i != 0) formatter.print(", ")!; @@ -133,21 +133,35 @@ fn Type? List.pop_first(&self) } <* - @require index < self.size : `Removed element out of bounds` + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn void List.remove_at(&self, usz index) +fn void List.remove_at(&self, sz index) { - usz new_size = self.size - 1; + sz new_size = self.size - 1; defer list_set_size(self, new_size); if (!new_size || index == new_size) return; self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size]; } +<* + Removes an element at "index" by copying the last element to this slot and reducing + the length by 1. + + @require index < self.size && index >= 0 : "Index out of bounds" +*> +fn void List.remove_unordered_at(&self, sz index) +{ + sz new_size = self.size - 1; + defer list_set_size(self, new_size); + if (!new_size || index == new_size) return; + self.entries[index] = self.entries[new_size]; +} + fn void List.add_all(&self, List* other_list) { if (!other_list.size) return; self.reserve(other_list.size); - usz index = list_set_size(self, self.size + other_list.size); + sz index = list_set_size(self, self.size + other_list.size); foreach (&value : other_list) { self.entries[index++] = *value; @@ -193,19 +207,6 @@ fn Type[] List.array_view(&self) return self.entries[:self.size]; } -<* - Add the values of an array to this list. - - @param [in] array - @ensure self.size >= array.len -*> -fn void List.add_array(&self, Type[] array) @deprecated("Use push_all") -{ - if (!array.len) return; - self.reserve(array.len); - usz index = list_set_size(self, self.size + array.len); - self.entries[index : array.len] = array[..]; -} <* Add the values of an array to this list. @@ -217,7 +218,7 @@ fn void List.push_all(&self, Type[] array) { if (!array.len) return; self.reserve(array.len); - usz index = list_set_size(self, self.size + array.len); + sz index = list_set_size(self, self.size + array.len); self.entries[index : array.len] = array[..]; } @@ -227,13 +228,13 @@ fn void List.push_front(&self, Type type) @inline } <* - @require index <= self.size : `Insert was out of bounds` + @require index <= self.size && index >= 0: `Insert was out of bounds` *> -fn void List.insert_at(&self, usz index, Type type) +fn void List.insert_at(&self, sz index, Type type) { self.reserve(1); list_set_size(self, self.size + 1); - for (isz i = self.size - 1; i > index; i--) + for (sz i = self.size - 1; i > index; i--) { self.entries[i] = self.entries[i - 1]; } @@ -241,9 +242,9 @@ fn void List.insert_at(&self, usz index, Type type) } <* - @require index < self.size + @require index < self.size && index >= 0 : "Index out of bounds" *> -fn void List.set_at(&self, usz index, Type type) +fn void List.set_at(&self, sz index, Type type) { self.entries[index] = type; } @@ -277,20 +278,20 @@ fn bool List.is_empty(&self) @inline return !self.size; } -fn usz List.byte_size(&self) @inline +fn sz List.byte_size(&self) @inline { - return Type.sizeof * self.size; + return Type::size * self.size; } -fn usz List.len(&self) @operator(len) @inline +fn sz List.len(&self) @operator(len) @inline { return self.size; } <* - @require index < self.size : `Access out of bounds` + @require index < self.size && index >= 0 : `Access out of bounds` *> -fn Type List.get(&self, usz index) @inline +fn Type List.get(&self, sz index) @inline { return self.entries[index]; } @@ -302,9 +303,9 @@ fn void List.free(&self) list_pre_free(self); // Remove sanitizer annotation $if type_is_overaligned(): - allocator::free_aligned(self.allocator, self.entries); + alloc::free_aligned(self.allocator, self.entries); $else - allocator::free(self.allocator, self.entries); + alloc::free(self.allocator, self.entries); $endif; self.capacity = 0; self.size = 0; @@ -312,9 +313,9 @@ fn void List.free(&self) } <* - @require i < self.size && j < self.size : `Access out of bounds` + @require i < self.size && i >= 0 && j < self.size && j >= 0 : `Access out of bounds` *> -fn void List.swap(&self, usz i, usz j) +fn void List.swap(&self, sz i, sz j) { @swap(self.entries[i], self.entries[j]); } @@ -323,7 +324,7 @@ fn void List.swap(&self, usz i, usz j) @param filter : "The function to determine if it should be removed or not" @return "the number of deleted elements" *> -fn usz List.remove_if(&self, ElementPredicate filter) +fn sz List.remove_if(&self, ElementPredicate filter) { return list_common::list_remove_if(self, filter, false); } @@ -332,14 +333,14 @@ fn usz List.remove_if(&self, ElementPredicate filter) @param selection : "The function to determine if it should be kept or not" @return "the number of deleted elements" *> -fn usz List.retain_if(&self, ElementPredicate selection) +fn sz List.retain_if(&self, ElementPredicate selection) { return list_common::list_remove_if(self, selection, true); } -fn usz List.remove_using_test(&self, ElementTest filter, any context) +fn sz List.remove_using_test(&self, ElementTest filter, any context) { - usz old_size = self.size; + sz old_size = self.size; defer { if (old_size != self.size) self._update_size_change(old_size, self.size); @@ -349,16 +350,16 @@ fn usz List.remove_using_test(&self, ElementTest filter, any context) -fn usz List.retain_using_test(&self, ElementTest filter, any context) +fn sz List.retain_using_test(&self, ElementTest filter, any context) { - usz old_size = self.size; + sz old_size = self.size; defer { if (old_size != self.size) self._update_size_change(old_size, self.size); } return list_common::list_remove_using_test(self, filter, true, context); } -fn void list_ensure_capacity(List* self, usz min_capacity) @local +fn void list_ensure_capacity(List* self, sz min_capacity) @local { if (!min_capacity) return; if (self.capacity >= min_capacity) return; @@ -378,9 +379,9 @@ fn void list_ensure_capacity(List* self, usz min_capacity) @local min_capacity = math::next_power_of_2(min_capacity); $if type_is_overaligned(): - self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, alignment: Type[1].alignof)!!; + self.entries = alloc::realloc_aligned(self.allocator, self.entries, Type::size * min_capacity, alignment: Type[1]::alignment)!!; $else - self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity); + self.entries = alloc::realloc(self.allocator, self.entries, Type::size * min_capacity); $endif; self.capacity = min_capacity; @@ -388,45 +389,45 @@ fn void list_ensure_capacity(List* self, usz min_capacity) @local } <* - @require index < self.size : `Access out of bounds` + @require index < self.size && index >= 0 : `Access out of bounds` *> -macro Type List.@item_at(&self, usz index) @operator([]) +macro Type List.@item_at(&self, sz index) @operator([]) { return self.entries[index]; } <* - @require index < self.size : `Access out of bounds` + @require index < self.size && index >= 0 : `Access out of bounds` *> -fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline +fn Type* List.get_ref(&self, sz index) @operator(&[]) @inline { return &self.entries[index]; } <* - @require index < self.size : `Access out of bounds` + @require index < self.size && index >= 0 : `Access out of bounds` *> -fn void List.set(&self, usz index, Type value) @operator([]=) +fn void List.set(&self, sz index, Type value) @operator([]=) { self.entries[index] = value; } -fn void List.reserve(&self, usz added) +fn void List.reserve(&self, sz added) { - usz new_size = self.size + added; + sz new_size = self.size + added; if (self.capacity >= new_size) return; - assert(new_size < usz.max / 2U); - usz new_capacity = self.capacity ? 2U * self.capacity : 16U; + assert(new_size < sz::max / 2U); + sz new_capacity = self.capacity ? 2U * self.capacity : 16U; while (new_capacity < new_size) new_capacity *= 2U; list_ensure_capacity(self, new_capacity); } -fn void List._update_size_change(&self,usz old_size, usz new_size) +fn void List._update_size_change(&self,sz old_size, sz new_size) { if (old_size == new_size) return; $if env::ADDRESS_SANITIZER: - if (self.allocator.ptr != &allocator::LIBC_ALLOCATOR) return; + if (self.allocator.ptr != &allocators::LIBC_ALLOCATOR) return; sanitizer::annotate_contiguous_container(self.entries, &self.entries[self.capacity], &self.entries[old_size], @@ -434,11 +435,12 @@ fn void List._update_size_change(&self,usz old_size, usz new_size) $endif } <* - @require new_size == 0 || self.capacity != 0 + @require new_size >= 0 : "The new size cannot be negative" + @require new_size == 0 || self.capacity > 0 *> -fn usz list_set_size(List* self, usz new_size) @inline @private +fn sz list_set_size(List* self, sz new_size) @inline @private { - usz old_size = self.size; + sz old_size = self.size; self._update_size_change(old_size, new_size); self.size = new_size; return old_size; @@ -461,7 +463,7 @@ macro void list_post_alloc(List* self) @private // Functions for equatable types -fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) +fn sz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) { foreach (i, v : self) { @@ -470,7 +472,7 @@ fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) return NOT_FOUND~; } -fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) +fn sz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) { foreach_r (i, v : self) { @@ -529,13 +531,10 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @param value : "The value to remove" @return "the number of deleted elements." *> -fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) +fn sz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) { - usz old_size = self.size; - defer - { - if (old_size != self.size) self._update_size_change(old_size, self.size); - } + sz old_size = self.size; + defer if (old_size != self.size) self._update_size_change(old_size, self.size); return list_common::list_remove_item(self, value); } @@ -544,10 +543,8 @@ fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE) { if (!other_list.size) return; - usz old_size = self.size; - defer { - if (old_size != self.size) self._update_size_change(old_size, self.size); - } + sz old_size = self.size; + defer if (old_size != self.size) self._update_size_change(old_size, self.size); foreach (v : other_list) self.remove_item(v); } @@ -555,19 +552,17 @@ fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE) @param [&in] self @return "The number non-null values in the list" *> -fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER) +fn sz List.compact_count(&self) @if(ELEMENT_IS_POINTER) { - usz vals = 0; + sz vals = 0; foreach (v : self) if (v) vals++; return vals; } -fn usz List.compact(&self) @if(ELEMENT_IS_POINTER) +fn sz List.compact(&self) @if(ELEMENT_IS_POINTER) { - usz old_size = self.size; - defer { - if (old_size != self.size) self._update_size_change(old_size, self.size); - } + sz old_size = self.size; + defer if (old_size != self.size) self._update_size_change(old_size, self.size); return list_common::list_compact(self); } diff --git a/test/stdlib/src/collections/list_common.c3 b/test/stdlib/src/collections/list_common.c3 index 021fa1f..d535f79 100644 --- a/test/stdlib/src/collections/list_common.c3 +++ b/test/stdlib/src/collections/list_common.c3 @@ -6,7 +6,7 @@ module std::collections::list_common; macro list_to_aligned_array($Type, self, Allocator allocator) { if (!self.size) return ($Type[]){}; - $Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size); + $Type[] result = alloc::alloc_array_aligned(allocator, $Type, self.size); result[..] = self.entries[:self.size]; return result; } @@ -14,7 +14,7 @@ macro list_to_aligned_array($Type, self, Allocator allocator) macro list_to_array($Type, self, Allocator allocator) { if (!self.size) return ($Type[]){}; - $Type[] result = allocator::alloc_array(allocator, $Type, self.size); + $Type[] result = alloc::alloc_array(allocator, $Type, self.size); result[..] = self.entries[:self.size]; return result; } @@ -22,18 +22,18 @@ macro list_to_array($Type, self, Allocator allocator) macro void list_reverse(self) { if (self.size < 2) return; - usz half = self.size / 2U; - usz end = self.size - 1; - for (usz i = 0; i < half; i++) + sz half = self.size / 2U; + sz end = self.size - 1; + for (sz i = 0; i < half; i++) { @swap(self.entries[i], self.entries[end - i]); } } -macro usz list_remove_using_test(self, filter, bool $invert, ctx) +macro sz list_remove_using_test(self, filter, bool $invert, ctx) { - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) + sz size = self.size; + for (sz i = size, sz k = size; k > 0; k = i) { // Find last index of item to be deleted. $if $invert: @@ -42,7 +42,7 @@ macro usz list_remove_using_test(self, filter, bool $invert, ctx) while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; $endif // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; + sz n = self.size - k; self.entries[i:n] = self.entries[k:n]; self.size -= k - i; // Find last index of item not to be deleted. @@ -55,13 +55,13 @@ macro usz list_remove_using_test(self, filter, bool $invert, ctx) return size - self.size; } -macro usz list_compact(self) +macro sz list_compact(self) { - usz size = self.size; - for (usz i = size; i > 0; i--) + sz size = self.size; + for (sz i = size; i > 0; i--) { if (self.entries[i - 1]) continue; - for (usz j = i; j < size; j++) + for (sz j = i; j < size; j++) { self.entries[j - 1] = self.entries[j]; } @@ -70,13 +70,13 @@ macro usz list_compact(self) return size - self.size; } -macro usz list_remove_item(self, value) +macro sz list_remove_item(self, value) { - usz size = self.size; - for (usz i = size; i > 0; i--) + sz size = self.size; + for (sz i = size; i > 0; i--) { if (!equals(self.entries[i - 1], value)) continue; - for (usz j = i; j < self.size; j++) + for (sz j = i; j < self.size; j++) { self.entries[j - 1] = self.entries[j]; } @@ -86,10 +86,10 @@ macro usz list_remove_item(self, value) } -macro usz list_remove_if(self, filter, bool $invert) +macro sz list_remove_if(self, filter, bool $invert) { - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) + sz size = self.size; + for (sz i = size, sz k = size; k > 0; k = i) { // Find last index of item to be deleted. $if $invert: @@ -98,7 +98,7 @@ macro usz list_remove_if(self, filter, bool $invert) while (i > 0 && filter(&self.entries[i - 1])) i--; $endif // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; + sz n = self.size - k; self.entries[i:n] = self.entries[k:n]; self.size -= k - i; // Find last index of item not to be deleted. diff --git a/test/stdlib/src/collections/maybe.c3 b/test/stdlib/src/collections/maybe.c3 index 201a2a4..83cf82a 100644 --- a/test/stdlib/src/collections/maybe.c3 +++ b/test/stdlib/src/collections/maybe.c3 @@ -7,7 +7,7 @@ struct Maybe (Printable) bool has_value; } -fn usz? Maybe.to_format(&self, Formatter* f) @dynamic +fn sz? Maybe.to_format(&self, Formatter* f) @dynamic { if (self.has_value) return f.printf("[%s]", self.value); return f.printf("[EMPTY]"); diff --git a/test/stdlib/src/collections/object.c3 b/test/stdlib/src/collections/object.c3 index f99efee..23aed86 100644 --- a/test/stdlib/src/collections/object.c3 +++ b/test/stdlib/src/collections/object.c3 @@ -4,9 +4,9 @@ module std::collections::object; import std::collections::map, std::collections::list, std::io; -const Object TRUE_OBJECT = { .b = true, .type = bool.typeid }; -const Object FALSE_OBJECT = { .b = false, .type = bool.typeid }; -const Object NULL_OBJECT = { .type = void*.typeid }; +const Object TRUE_OBJECT = { .b = true, .type = bool::typeid }; +const Object FALSE_OBJECT = { .b = false, .type = bool::typeid }; +const Object NULL_OBJECT = { .type = void*::typeid }; struct Object (Printable) { @@ -14,7 +14,7 @@ struct Object (Printable) Allocator allocator; union { - uint128 i; + int128 i; double f; bool b; String s; @@ -25,7 +25,7 @@ struct Object (Printable) } -fn usz? Object.to_format(&self, Formatter* formatter) @dynamic +fn sz? Object.to_format(&self, Formatter* formatter) @dynamic { switch (self.type) { @@ -38,7 +38,7 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic case bool: return formatter.printf(self.b ? "true" : "false")!; case ObjectInternalList: - usz n = formatter.printf("[")!; + sz n = formatter.printf("[")!; foreach (i, ol : self.array) { if (i > 0) n += formatter.printf(",")!; @@ -47,7 +47,7 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic n += formatter.printf("]")!; return n; case ObjectInternalMap: - usz n = formatter.printf("{")!; + sz n = formatter.printf("{")!; @stack_mem(1024; Allocator mem) { foreach (i, key : self.map.keys(mem)) @@ -60,7 +60,7 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic n += formatter.printf("}")!; return n; default: - switch (self.type.kindof) + switch (self.type.kind) { case SIGNED_INT: return formatter.printf("%d", (int128)self.i)!; @@ -78,7 +78,7 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic fn Object* new_obj(Allocator allocator) { - return @alloc_obj(allocator, { .allocator = allocator, .type = void.typeid }); + return @alloc_obj(allocator, { .allocator = allocator, .type = void::typeid }); } fn Object* new_null() @@ -86,24 +86,24 @@ fn Object* new_null() return &NULL_OBJECT; } -fn Object* new_int(int128 i, Allocator allocator) +fn Object* new_int(Allocator allocator, int128 i) { - return @alloc_obj(allocator, { .i = i, .allocator = allocator, .type = int128.typeid }); + return @alloc_obj(allocator, { .i = i, .allocator = allocator, .type = int128::typeid }); } -macro Object* new_enum(e, Allocator allocator) +macro Object* new_enum(Allocator allocator, e) { - return @alloc_obj(allocator, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) }); + return @alloc_obj(allocator, { .i = e, .allocator = allocator, .type = @typeid(e) }); } -fn Object* new_float(double f, Allocator allocator) +fn Object* new_float(Allocator allocator, double f) { - return @alloc_obj(allocator, { .f = f, .allocator = allocator, .type = double.typeid }); + return @alloc_obj(allocator, { .f = f, .allocator = allocator, .type = double::typeid }); } -fn Object* new_string(String s, Allocator allocator) +fn Object* new_string(Allocator allocator, String s) { - return @alloc_obj(allocator, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid }); + return @alloc_obj(allocator, { .s = s.copy(allocator), .allocator = allocator, .type = String::typeid }); } @@ -119,7 +119,7 @@ fn void Object.free(&self) case void: break; case String: - allocator::free(self.allocator, self.s); + alloc::free(self.allocator, self.s); case ObjectInternalList: foreach (ol : self.array) { @@ -136,22 +136,22 @@ fn void Object.free(&self) } if (self.allocator) { - $if Object.alignof > mem::DEFAULT_MEM_ALIGNMENT: - allocator::free_aligned(self.allocator, self); + $if Object::alignment > mem::DEFAULT_MEM_ALIGNMENT: + alloc::free_aligned(self.allocator, self); $else - allocator::free(self.allocator, self); + alloc::free(self.allocator, self); $endif } } fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT; -fn bool Object.is_empty(&self) @inline => self.type == void.typeid; -fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid; -fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid; -fn bool Object.is_bool(&self) @inline => self.type == bool.typeid; -fn bool Object.is_string(&self) @inline => self.type == String.typeid; -fn bool Object.is_float(&self) @inline => self.type == double.typeid; -fn bool Object.is_int(&self) @inline => self.type == int128.typeid; +fn bool Object.is_empty(&self) @inline => self.type == void::typeid; +fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap::typeid; +fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList::typeid; +fn bool Object.is_bool(&self) @inline => self.type == bool::typeid; +fn bool Object.is_string(&self) @inline => self.type == String::typeid; +fn bool Object.is_float(&self) @inline => self.type == double::typeid; +fn bool Object.is_int(&self) @inline => self.type == int128::typeid; fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map(); fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array(); @@ -162,7 +162,7 @@ fn void object_init_map_if_needed(Object* self) @private { if (self.is_empty()) { - self.type = ObjectInternalMap.typeid; + self.type = ObjectInternalMap::typeid; self.map.init(self.allocator); } } @@ -174,7 +174,7 @@ fn void object_init_array_if_needed(Object* self) @private { if (self.is_empty()) { - self.type = ObjectInternalList.typeid; + self.type = ObjectInternalList::typeid; self.array.init(self.allocator); } } @@ -199,19 +199,19 @@ macro Object* Object.object_from_value(&self, value) @private var $Type = $typeof(value); $switch: $case types::is_int($Type): - return new_int(value, self.allocator); + return new_int(self.allocator, value); $case types::is_float($Type): - return new_float(value, self.allocator); - $case $Type.typeid == String.typeid: - return new_string(value, self.allocator); - $case $Type.typeid == bool.typeid: + return new_float(self.allocator, value); + $case $Type::typeid == String::typeid: + return new_string(self.allocator, value); + $case $Type::typeid == bool::typeid: return new_bool(value); - $case $Type.typeid == Object*.typeid: + $case $Type::typeid == Object*::typeid: return value; - $case $Type.typeid == void*.typeid: + $case $Type::typeid == void*::typeid: return &NULL_OBJECT; $case $defined(String x = value): - return new_string(value, self.allocator); + return new_string(self.allocator, value); $default: $error "Unsupported object type."; $endswitch @@ -228,7 +228,7 @@ macro Object* Object.set(&self, String key, value) <* @require self.is_indexable() *> -macro Object* Object.set_at(&self, usz index, String key, value) +macro Object* Object.set_at(&self, sz index, String key, value) { Object* val = self.object_from_value(value); self.set_object_at(key, index, val); @@ -253,10 +253,23 @@ fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key); +<* + Iterate over all key/value pairs in this object when it is a map. + Does nothing if the object is not a map. +*> +macro void Object.@each_entry(&self; @body(String key, Object* value)) +{ + if (!self.is_map()) return; + self.map.@each_entry(; ObjectInternalMapEntry* e) + { + @body(e.key, e.value); + }; +} + <* @require self.is_indexable() *> -fn Object* Object.get_at(&self, usz index) +fn Object* Object.get_at(&self, sz index) { return self.array.get(index); } @@ -264,7 +277,7 @@ fn Object* Object.get_at(&self, usz index) <* @require self.is_indexable() *> -fn usz Object.get_len(&self) +fn sz Object.get_len(&self) { return self.array.len(); } @@ -281,7 +294,7 @@ fn void Object.push_object(&self, Object* to_append) <* @require self.is_indexable() *> -fn void Object.set_object_at(&self, usz index, Object* to_set) +fn void Object.set_object_at(&self, sz index, Object* to_set) { object_init_array_if_needed(self); while (self.array.len() < index) @@ -298,7 +311,7 @@ fn void Object.set_object_at(&self, usz index, Object* to_set) } <* - @require $Type.kindof.is_int() : "Expected an integer type." + @require $Type::kind.is_int() : "Expected an integer type." *> macro get_integer_value(Object* value, $Type) { @@ -308,7 +321,7 @@ macro get_integer_value(Object* value, $Type) } if (value.is_string()) { - $if $Type.kindof == TypeKind.SIGNED_INT: + $if $Type::kind == TypeKind.SIGNED_INT: return ($Type)value.s.to_int128(); $else return ($Type)value.s.to_uint128(); @@ -321,16 +334,16 @@ macro get_integer_value(Object* value, $Type) <* @require self.is_indexable() - @require $Type.kindof.is_int() : "Expected an integer type" + @require $Type::kind.is_int() : "Expected an integer type" *> -macro Object.get_integer_at(&self, $Type, usz index) @private +macro Object.get_integer_at(&self, $Type, sz index) @private { return get_integer_value(self.get_at(index), $Type); } <* @require self.is_keyable() - @require $Type.kindof.is_int() : "Expected an integer type" + @require $Type::kind.is_int() : "Expected an integer type" *> macro Object.get_integer(&self, $Type, String key) @private { @@ -343,23 +356,23 @@ fn int? Object.get_int(&self, String key) => self.get_integer(int, key); fn long? Object.get_long(&self, String key) => self.get_integer(long, key); fn int128? Object.get_int128(&self, String key) => self.get_integer(int128, key); -fn ichar? Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index); -fn short? Object.get_short_at(&self, usz index) => self.get_integer_at(short, index); -fn int? Object.get_int_at(&self, usz index) => self.get_integer_at(int, index); -fn long? Object.get_long_at(&self, usz index) => self.get_integer_at(long, index); -fn int128? Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index); +fn ichar? Object.get_ichar_at(&self, sz index) => self.get_integer_at(ichar, index); +fn short? Object.get_short_at(&self, sz index) => self.get_integer_at(short, index); +fn int? Object.get_int_at(&self, sz index) => self.get_integer_at(int, index); +fn long? Object.get_long_at(&self, sz index) => self.get_integer_at(long, index); +fn int128? Object.get_int128_at(&self, sz index) => self.get_integer_at(int128, index); -fn char? Object.get_char(&self, String key) => self.get_integer(ichar, key); -fn short? Object.get_ushort(&self, String key) => self.get_integer(ushort, key); +fn char? Object.get_char(&self, String key) => self.get_integer(char, key); +fn ushort? Object.get_ushort(&self, String key) => self.get_integer(ushort, key); fn uint? Object.get_uint(&self, String key) => self.get_integer(uint, key); fn ulong? Object.get_ulong(&self, String key) => self.get_integer(ulong, key); fn uint128? Object.get_uint128(&self, String key) => self.get_integer(uint128, key); -fn char? Object.get_char_at(&self, usz index) => self.get_integer_at(char, index); -fn ushort? Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index); -fn uint? Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index); -fn ulong? Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index); -fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index); +fn char? Object.get_char_at(&self, sz index) => self.get_integer_at(char, index); +fn ushort? Object.get_ushort_at(&self, sz index) => self.get_integer_at(ushort, index); +fn uint? Object.get_uint_at(&self, sz index) => self.get_integer_at(uint, index); +fn ulong? Object.get_ulong_at(&self, sz index) => self.get_integer_at(ulong, index); +fn uint128? Object.get_uint128_at(&self, sz index) => self.get_integer_at(uint128, index); <* @require self.is_keyable() @@ -374,7 +387,7 @@ fn String? Object.get_string(&self, String key) <* @require self.is_indexable() *> -fn String? Object.get_string_at(&self, usz index) +fn String? Object.get_string_at(&self, sz index) { Object* value = self.get_at(index); if (!value.is_string()) return TYPE_MISMATCH~; @@ -387,17 +400,17 @@ fn String? Object.get_string_at(&self, usz index) macro String? Object.get_enum(&self, $EnumType, String key) { Object value = self.get(key)!; - if ($EnumType.typeid != value.type) return TYPE_MISMATCH~; + if ($EnumType::typeid != value.type) return TYPE_MISMATCH~; return ($EnumType)value.i; } <* @require self.is_indexable() *> -macro String? Object.get_enum_at(&self, $EnumType, usz index) +macro String? Object.get_enum_at(&self, $EnumType, sz index) { Object value = self.get_at(index); - if ($EnumType.typeid != value.type) return TYPE_MISMATCH~; + if ($EnumType::typeid != value.type) return TYPE_MISMATCH~; return ($EnumType)value.i; } @@ -415,7 +428,7 @@ fn bool? Object.get_bool(&self, String key) <* @require self.is_indexable() *> -fn bool? Object.get_bool_at(&self, usz index) +fn bool? Object.get_bool_at(&self, sz index) { Object* value = self.get_at(index); if (!value.is_bool()) return TYPE_MISMATCH~; @@ -428,7 +441,7 @@ fn bool? Object.get_bool_at(&self, usz index) fn double? Object.get_float(&self, String key) { Object* value = self.get(key)!; - switch (value.type.kindof) + switch (value.type.kind) { case SIGNED_INT: return (double)value.i; @@ -444,10 +457,10 @@ fn double? Object.get_float(&self, String key) <* @require self.is_indexable() *> -fn double? Object.get_float_at(&self, usz index) +fn double? Object.get_float_at(&self, sz index) { Object* value = self.get_at(index); - switch (value.type.kindof) + switch (value.type.kind) { case SIGNED_INT: return (double)value.i; @@ -474,10 +487,9 @@ alias ObjectInternalMapEntry @private = Entry {String, Object*}; macro Object* @alloc_obj(Allocator a, #init) @private { - $if Object.alignof > mem::DEFAULT_MEM_ALIGNMENT: - return allocator::new_aligned(a, Object, #init)!!; + $if Object::alignment > mem::DEFAULT_MEM_ALIGNMENT: + return alloc::new_aligned(a, Object, #init)!!; $else - return allocator::new(a, Object, #init); + return alloc::new(a, Object, #init); $endif } - diff --git a/test/stdlib/src/collections/priorityqueue.c3 b/test/stdlib/src/collections/priorityqueue.c3 index 8e63684..68d8e1b 100644 --- a/test/stdlib/src/collections/priorityqueue.c3 +++ b/test/stdlib/src/collections/priorityqueue.c3 @@ -31,13 +31,13 @@ struct PrivatePriorityQueue (Printable) List{Type} heap; } -fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline +fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, sz initial_capacity = 16, ) @inline { self.heap.init(allocator, initial_capacity); return self; } -fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline +fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, sz initial_capacity = 16) @inline { self.init(tmem, initial_capacity); return self; @@ -47,10 +47,10 @@ fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity fn void PrivatePriorityQueue.push(&self, Type element) { self.heap.push(element); - usz i = self.heap.len() - 1; + sz i = self.heap.len() - 1; while (i > 0) { - usz parent = (i - 1) / 2; + sz parent = (i - 1) / 2; Type item = self.heap[i]; Type parent_item = self.heap[parent]; $if MAX: @@ -67,7 +67,7 @@ fn void PrivatePriorityQueue.push(&self, Type element) <* @require index < self.len() : "Index out of range" *> -fn void PrivatePriorityQueue.remove_at(&self, usz index) +fn void PrivatePriorityQueue.remove_at(&self, sz index) { if (index == 0) { @@ -81,14 +81,14 @@ fn void PrivatePriorityQueue.remove_at(&self, usz index) *> fn Type? PrivatePriorityQueue.pop(&self) { - usz i = 0; - usz len = self.heap.len(); + sz i = 0; + sz len = self.heap.len(); if (!len) return NO_MORE_ELEMENT~; - usz new_count = len - 1; + sz new_count = len - 1; self.heap.swap(0, new_count); while OUTER: ((2 * i + 1) < new_count) { - usz j = 2 * i + 1; + sz j = 2 * i + 1; Type left = self.heap[j]; Type item = self.heap[i]; switch @@ -127,7 +127,7 @@ fn void PrivatePriorityQueue.free(&self) self.heap.free(); } -fn usz PrivatePriorityQueue.len(&self) @operator(len) +fn sz PrivatePriorityQueue.len(&self) @operator(len) { return self.heap.len() @inline; } @@ -140,12 +140,12 @@ fn bool PrivatePriorityQueue.is_empty(&self) <* @require index < self.len() *> -fn Type PrivatePriorityQueue.get(&self, usz index) @operator([]) +fn Type PrivatePriorityQueue.get(&self, sz index) @operator([]) { return self.heap[index]; } -fn usz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic +fn sz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic { return self.heap.to_format(formatter); } diff --git a/test/stdlib/src/collections/range.c3 b/test/stdlib/src/collections/range.c3 index 11ef3e2..743d7e0 100644 --- a/test/stdlib/src/collections/range.c3 +++ b/test/stdlib/src/collections/range.c3 @@ -1,5 +1,5 @@ <* - @require Type.is_ordered : "The type must be ordered" + @require Type::is_ordered : "The type must be ordered" *> module std::collections::range ; import std::io; @@ -10,10 +10,10 @@ struct Range (Printable) Type end; } -fn usz Range.len(&self) @operator(len) +fn sz Range.len(&self) @operator(len) { if (self.end < self.start) return 0; - return (usz)(self.end - self.start) + 1; + return (sz)(self.end - self.start) + 1; } fn bool Range.contains(&self, Type value) @inline @@ -24,12 +24,12 @@ fn bool Range.contains(&self, Type value) @inline <* @require index < self.len() : "Can't index into an empty range" *> -fn Type Range.get(&self, usz index) @operator([]) +fn Type Range.get(&self, sz index) @operator([]) { - return (Type)(self.start + (usz)index); + return (Type)(self.start + (Type)index); } -fn usz? Range.to_format(&self, Formatter* formatter) @dynamic +fn sz? Range.to_format(&self, Formatter* formatter) @dynamic { return formatter.printf("[%s..%s]", self.start, self.end)!; } @@ -40,10 +40,10 @@ struct ExclusiveRange (Printable) Type end; } -fn usz ExclusiveRange.len(&self) @operator(len) +fn sz ExclusiveRange.len(&self) @operator(len) { if (self.end < self.start) return 0; - return (usz)(self.end - self.start); + return (sz)(self.end - self.start); } fn bool ExclusiveRange.contains(&self, Type value) @inline @@ -51,7 +51,7 @@ fn bool ExclusiveRange.contains(&self, Type value) @inline return value >= self.start && value < self.end; } -fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic +fn sz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic { return formatter.printf("[%s..<%s]", self.start, self.end)!; } @@ -59,7 +59,7 @@ fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic <* @require index < self.len() : "Can't index into an empty range" *> -fn Type ExclusiveRange.get(&self, usz index) @operator([]) +fn Type ExclusiveRange.get(&self, sz index) @operator([]) { - return (Type)(self.start + index); + return (Type)(self.start + (Type)index); } diff --git a/test/stdlib/src/collections/result.c3 b/test/stdlib/src/collections/result.c3 new file mode 100644 index 0000000..2bfdbf3 --- /dev/null +++ b/test/stdlib/src/collections/result.c3 @@ -0,0 +1,143 @@ +module std::collections::result ; +import std::io; + +faultdef RESULT_ERROR, RESULT_OK; + +<* + Result{OkType, ErrType} holds either a success value or an error value with a payload. + + **Prefer C3 optionals for normal error handling.** + C3's built-in optional/fault system handles most error cases with less overhead. + See: https://c3-lang.org/language-common/optionals-essential/ + + Use Result only when the error path must carry structured data that a bare fault cannot express. + + Good Example: + ```c3 + struct ParseError { int line; String msg; } + + fn Result{int, ParseError} parse_number(String s) + { + int! v = s.to_int(); + if (catch v) return result::err({ .line = 1, .msg = string::tformat("not a number: %s", s) }); + return result::ok(v); + } + + fn void main() + { + Result{int, ParseError} r = parse_number("42"); + if (try int v = r.ok()) + { + io::printfn("got %d", v); + } + + Result{int, ParseError} bad = parse_number("abc"); + if (try ParseError e = bad.err()) + { + io::printfn("error on line %d: %s", e.line, e.msg); + } + } + ``` + + Bad Example (should be a fault): + ```c3 + fn Result{int, String} div(int a, int b) + { + if (b == 0) return result::err("division by zero"); + return result::ok(a / b); + } + + fn void print_int(int x) + { + io::printfn("Integer %d", x); + } + + fn void main() + { + print_int(div(4, 1).ok())!!; + print_int(div(4, 0).ok())!!; + } + ``` + *> +struct Result (Printable) +{ + union + { + OkType value; + ErrType error; + } + bool is_ok; +} + +fn Result ok(OkType val) => {.is_ok = true, .value = val}; +fn Result err(ErrType err) => {.is_ok = false, .error = err}; + +fn sz? Result.to_format(&self, Formatter* f) @dynamic +{ + if (self.is_ok) return f.printf("OK[%s]", self.value); + return f.printf("ERR[%s]", self.error); +} + +<* + Returns the ok value or a fault + + @return "The ok value of the result" + @return? RESULT_ERROR +*> +fn OkType? Result.ok(&self) +{ + return self.is_ok ? self.value : RESULT_ERROR~; +} + +<* + Returns the error value or a fault + + @return "The error of the result" + @return? RESULT_OK +*> +fn ErrType? Result.err(&self) +{ + return self.is_ok ? RESULT_OK~ : self.error; +} + +fn bool Result.equals(self, Result other) @operator(==) +@if(types::is_equatable_type(OkType) &&& types::is_equatable_type(ErrType)) +{ + if (self.is_ok) + { + return other.is_ok && equals(self.value, other.value); + } + return !other.is_ok && equals(self.error, other.error); +} + +<* + This macro allows the result to diverge on error (return/break/continue) + + **Aborts the program if the body doesn't diverge** + + Examples: + ```c3 + fn Result{int, String} foo() + { + Result{int, String} res = result::err("Error"); + int value = res.@or_else(;it) { return it; }; // early function return + // ... + } + + fn Result{int, String} bar() + { + Result{int, String} res = result::ok(22); + int value = res.@or_else(;it) { return it; }; + // value is 22 + } + ``` +*> +macro OkType Result.@or_else(&self; @body(Result res)) +{ + if (!self.is_ok) + { + @body(*self); + unreachable("@or_else must diverge on error."); + } + return self.value; +} diff --git a/test/stdlib/src/collections/ringbuffer.c3 b/test/stdlib/src/collections/ringbuffer.c3 deleted file mode 100644 index 975f12c..0000000 --- a/test/stdlib/src/collections/ringbuffer.c3 +++ /dev/null @@ -1,115 +0,0 @@ -<* - @require Type.kindof == ARRAY : "Required an array type" -*> -module std::collections::ringbuffer ; -import std::io; - -alias Element = $typeof((Type){}[0]); - -struct RingBuffer (Printable) -{ - Type buf; - usz written; - usz head; -} - -fn void RingBuffer.init(&self) @inline -{ - *self = {}; -} - -fn void RingBuffer.push(&self, Element c) -{ - if (self.written < self.buf.len) - { - self.buf[self.written] = c; - self.written++; - } - else - { - self.buf[self.head] = c; - self.head = (self.head + 1) % self.buf.len; - } -} - -fn Element RingBuffer.get(&self, usz index) @operator([]) -{ - index %= self.buf.len; - usz avail = self.buf.len - self.head; - if (index < avail) - { - return self.buf[self.head + index]; - } - return self.buf[index - avail]; -} - -fn Element? RingBuffer.pop(&self) -{ - switch - { - case self.written == 0: - return NO_MORE_ELEMENT~; - case self.written < self.buf.len: - self.written--; - return self.buf[self.written]; - default: - self.head = (self.head - 1) % self.buf.len; - return self.buf[self.head]; - } -} - -fn usz? RingBuffer.to_format(&self, Formatter* format) @dynamic -{ - // Improve this? - return format.printf("%s", self.buf); -} - -fn usz RingBuffer.read(&self, usz index, Element[] buffer) -{ - index %= self.buf.len; - if (self.written < self.buf.len) - { - if (index >= self.written) return 0; - usz end = self.written - index; - usz n = min(end, buffer.len); - buffer[:n] = self.buf[index:n]; - return n; - } - usz end = self.buf.len - self.head; - if (index >= end) - { - index -= end; - if (index >= self.head) return 0; - usz n = min(self.head - index, buffer.len); - buffer[:n] = self.buf[index:n]; - return n; - } - if (buffer.len <= self.buf.len - index) - { - usz n = buffer.len; - buffer[:n] = self.buf[self.head + index:n]; - return n; - } - usz n1 = self.buf.len - index; - buffer[:n1] = self.buf[self.head + index:n1]; - buffer = buffer[n1..]; - index -= n1; - usz n2 = min(self.head - index, buffer.len); - buffer[:n2] = self.buf[index:n2]; - return n1 + n2; -} - -fn void RingBuffer.write(&self, Element[] buffer) -{ - usz i; - while (self.written < self.buf.len && i < buffer.len) - { - self.buf[self.written] = buffer[i++]; - self.written++; - } - foreach (c : buffer[i..]) - { - self.buf[self.head] = c; - self.head = (self.head + 1) % self.buf.len; - } -} \ No newline at end of file diff --git a/test/stdlib/src/collections/ringlist.c3 b/test/stdlib/src/collections/ringlist.c3 new file mode 100644 index 0000000..6b01254 --- /dev/null +++ b/test/stdlib/src/collections/ringlist.c3 @@ -0,0 +1,246 @@ +<* + @require types::is_int($typeof(CAPACITY)) && CAPACITY > 0 : "Capacity must be bigger than 0" +*> +module std::collections::ringlist ; +import std::io, std::math; + +<* + Ring list is a queue-like, FIFO, fixed size (stack allocated) data structure. + The useful property of a ring list is that it does not need to have its elements shuffled around (mem::move) when one is consumed by 'pop' operation. + User can 'push' indefinitely, but subsequent pushes can override the oldest values. + tail - points to the next write location (push). + head - points to the current read location (pop). +*> +struct RingList (Printable) +{ + Type[CAPACITY] buf; + sz head; + sz tail; +} + +fn void RingList.init(&self) @inline +{ + *self = { }; +} + +fn void RingList.init_from_values(&self, Type[] values) +{ + *self = { }; + self.push_all(values); +} + +<* + Pushes a new element to the end of the ring list. +*> +fn void RingList.push(&self, Type element) +{ + self.buf[self.tail] = element; + @wrap_inc(self.tail); + if (self.tail == self.head) @wrap_inc(self.head); +} + +<* + Pops the element from the start of the ring list. +*> +fn Type? RingList.pop_first(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + return _pop(self); +} + +<* + Performs subsequent 'push' calls on each value in the array or slice + + @param [in] array : "Array to push elements from" +*> +fn void RingList.push_all(&self, Type[] array) +{ + foreach (e : array) self.push(e); +} + +<* + Performs subsequent 'pop' calls and writes the output into 'target' + Count of the popped elements calculated by taking the 'min' + on the input array length and ring lists's internal length + + @param [out] target : "Slice into which data will be read" + @return "The resulting slice" +*> +fn Type[] RingList.pop_first_into(&self, Type[] target) +{ + if (@unlikely(self.is_empty())) return target[:0]; + sz len = min(target.len, self.len()); + for (sz i = 0; i < len; i++) + { + target[i] = _pop(self); + } + return target[:len]; +} + +<* + Returns the element at the start of the ring list. +*> +fn Type? RingList.first(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + return self.buf[self.head]; +} + +<* + Returns a reference to the element at the start of the ring list. +*> +fn Type*? RingList.first_ref(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + return &self.buf[self.head]; +} + +<* + Returns the element at the end of the ring list. +*> +fn Type? RingList.last(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + return self.buf[(self.tail ?: self.buf.len) - 1]; +} + +<* + Returns a reference to the element at the end of the ring list. +*> +fn Type*? RingList.last_ref(&self) +{ + if (@unlikely(self.is_empty())) return NO_MORE_ELEMENT~; + return &self.buf[(self.tail ?: self.buf.len) - 1]; +} + +<* + @require index >= 0 && index < self.len() : `Access out of bounds` +*> +fn Type RingList.get(&self, sz index) @operator([]) @inline +{ + sz idx = wrap_add(self.head, index); + return self.buf[idx]; +} + +<* + @require index >= 0 && index < self.len() : `Access out of bounds` +*> +fn Type* RingList.get_ref(&self, sz index) @operator(&[]) @inline +{ + sz idx = wrap_add(self.head, index); + return &self.buf[idx]; +} + +<* + @require index >= 0 && index < self.len() : `Access out of bounds` +*> +fn void RingList.set(&self, sz index, Type value) @operator([]=) @inline +{ + sz idx = wrap_add(self.head, index); + self.buf[idx] = value; +} + +<* + Calculates count of elements in ring list. +*> +fn sz RingList.len(&self) @operator(len) +{ + return self.head > self.tail ? self.tail + CAPACITY - self.head : self.tail - self.head; +} + +<* + Tells if the ring list is currently empty. +*> +fn bool RingList.is_empty(&self) @inline +{ + return self.head == self.tail; +} + +<* + Tells if the ring list is currently full, it is full 1 slot behind actually being full or we can't detect it. +*> +fn bool RingList.is_full(&self) @inline +{ + return self.head > 0 ? self.tail == self.head - 1 : self.tail == CAPACITY - 1; +} + +<* + Reset the ring list's length and offset to zero, letting it write new elements over + old memory, in effect clearing the accessible contents. +*> +fn void RingList.clear(&self) @inline +{ + self.head = 0; + self.tail = 0; +} + +<* + Allocates a slice and copies the contents from the ring list in push order. + The user is responsible for freeing the memory. +*> +fn Type[]? RingList.copy_to_array(&self, Allocator alloc) +{ + if (@unlikely(self.is_empty())) return {}; + + sz len = self.len(); + Type[] slice = alloc::alloc_array(alloc, Type, len); + if (self.tail > self.head) + { + slice[..] = self.buf[self.head:len]; + return slice; + } + sz front_tail = CAPACITY - self.head; + slice[:front_tail] = self.buf[self.head:front_tail]; + slice[front_tail..] = self.buf[0:len - front_tail]; + return slice; +} + +fn sz? RingList.to_format(&self, Formatter* formatter) @dynamic +{ + if (self.tail >= self.head) + { + return formatter.printf("%s", self.buf[self.head..self.tail - 1]); + } + sz front_tail = CAPACITY - self.head; + sz total = formatter.printf("[%s", self.buf[self.head])!; + foreach (e : self.buf[self.head + 1:front_tail - 1]) + { + total += formatter.printf(", %s", e)!; + } + foreach (e : self.buf[0..self.tail - 1]) + { + total += formatter.printf(", %s", e)!; + } + return total + formatter.print("]"); +} + +// ----- Private API ----- + +<* + More optimized version of 'pop', when we know that the size is not 0. + Not for public use. + + @require self.len() > 0 : "Should be bigger than zero" +*> +fn Type _pop(RingList* self) @local +{ + defer @wrap_inc(self.head); + return self.buf[self.head]; +} + +macro void @wrap_inc(sz #value) @local +{ + #value = #value == CAPACITY - 1 ? 0 : #value + 1; +} + +<* + @require add >= 0 : "Should be equal or greater than 0" +*> +macro sz wrap_add(sz value, sz add) @local +{ + $if math::@is_power_of_2(CAPACITY): + return (value + add) & (CAPACITY - 1); + $else + return (value + add) % CAPACITY; + $endif +} diff --git a/test/stdlib/src/collections/sortedmap.c3 b/test/stdlib/src/collections/sortedmap.c3 new file mode 100644 index 0000000..badda53 --- /dev/null +++ b/test/stdlib/src/collections/sortedmap.c3 @@ -0,0 +1,610 @@ +// sortedmap.c3 +// An sorted map using a skip list for C3. +// +// Copyright (c) 2026 Christian Reifberger +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +module std::collections::sortedmap ; +import std::io, std::math::random; +import std::core::mem::alloc; + + +const int MAX_LEVEL = 32; + +int dummy @local; +const Allocator ONHEAP_ALLOCATOR = (Allocator)&dummy; + +struct SkipNode +{ + Key key; + Value value; + int level; + SkipNode*[*] forward; +} + +// CmpFn is a comparator: returns true if a should come before b in the list. +alias CmpFn = fn bool(Key, Key); + +struct Entry +{ + Key key; + Value value; +} + +<* + @require types::@comparable_value((Key){}) : "The Key must support ordered comparison" +*> +struct SortedMap (Printable) @mustinit +{ + Allocator allocator; + SkipNode* head; + int current_level; + sz count; + SimpleRandom rng; + CmpFn custom_cmp; // null = use built-in less() ordering +} + +const SortedMap ONHEAP = { .allocator = ONHEAP_ALLOCATOR }; + +struct SortedMapIterator +{ + SortedMap* map; + SkipNode* current; + sz index; +} + +struct SortedMapRangeIterator +{ + SortedMap* map; + SkipNode* start_node; + SkipNode* current; + sz current_idx; + sz count; +} + +// Returns true if 'a' should come before 'b' in the list. +macro bool sortedmap_cmp(custom_cmp, a, b) @private +{ + if (custom_cmp != null) return custom_cmp(a, b); + return less(a, b); +} + +<* + @require !self.is_initialized() : "Map was already initialized" +*> +fn SortedMap* SortedMap.init(&self, Allocator allocator) +{ + *self = { .allocator = allocator }; + // Single allocation for head + forward slice, calloc for zeroing of slice pointers + self.head = alloc::new_with_padding(allocator, SkipNode, SkipNode*[MAX_LEVEL]::size)!!; + random::seed_entropy(&self.rng); + return self; +} + +<* + @require !self.is_initialized() : "Map was already initialized" +*> +fn SortedMap* SortedMap.tinit(&self) +{ + return self.init(tmem) @inline; +} + +<* + Init with a custom comparator. The comparator replaces the built-in ordering. + @param cmp_fn : "Returns true if a should come before b in iteration order." + @require !self.is_initialized() : "Map was already initialized" +*> +fn SortedMap* SortedMap.init_with_cmp(&self, Allocator allocator, CmpFn cmp_fn) +{ + self.init(allocator); + self.custom_cmp = cmp_fn; + return self; +} + +<* + @require !self.is_initialized() : "Map was already initialized" +*> +fn SortedMap* SortedMap.tinit_with_cmp(&self, CmpFn cmp_fn) +{ + return self.init_with_cmp(tmem, cmp_fn) @inline; +} + +fn bool SortedMap.is_initialized(&self) +{ + return self.allocator && self.allocator.ptr != &dummy; +} + +fn int SortedMap.random_level(&self) +{ + int limit_level = min(MAX_LEVEL, self.current_level + 2); + int level = 1; + while (level < limit_level && (self.rng.next_int() & 1) == 0) level++; + return level; +} + +<* + Insert or update the value associated with key. +*> +fn bool SortedMap.set(&self, Key key, Value value) @operator([]=) +{ + // If the map isn't initialized, use the defaults to initialize it. + switch (self.allocator.ptr) + { + case &dummy: + self.init(mem); + case null: + self.tinit(); + default: + break; + } + SkipNode*[MAX_LEVEL] update; + SkipNode* current = self.head; + + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + update[i] = current; + } + + current = current.forward[0]; + + // Key already exists -- update value + if (current && !sortedmap_cmp(self.custom_cmp, current.key, key) && !sortedmap_cmp(self.custom_cmp, key, current.key)) + { + current.value = value; + return true; + } + + // Insert new node + int new_level = self.random_level(); + if (new_level > self.current_level) + { + update[self.current_level..new_level - 1] = self.head; + self.current_level = new_level; + } + + // Single allocation for node + forward slice + SkipNode* node = alloc::alloc_with_padding(self.allocator, SkipNode, new_level * SkipNode*::size)!!; + *node = { .key = key, .value = value, .level = new_level }; + + for (int i = 0; i < new_level; i++) + { + node.forward[i] = update[i].forward[i]; + update[i].forward[i] = node; + } + + self.count++; + return false; +} + +<* + @return "the value for key, or NOT_FOUND if absent" +*> +fn Value? SortedMap.get(&self, Key key) @operator([]) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + current = current.forward[0]; + if (current && !sortedmap_cmp(self.custom_cmp, current.key, key) && !sortedmap_cmp(self.custom_cmp, key, current.key)) + { + return current.value; + } + return NOT_FOUND~; +} + +<* + @return "a pointer to the value for key, or NOT_FOUND if absent" +*> +fn Value*? SortedMap.get_ref(&self, Key key) @operator(&[]) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + current = current.forward[0]; + if (current && !sortedmap_cmp(self.custom_cmp, current.key, key) && !sortedmap_cmp(self.custom_cmp, key, current.key)) + { + return ¤t.value; + } + return NOT_FOUND~; +} + +<* + Returns the value for key; inserts and returns #default_value if key is absent. + #default_value is only evaluated if the key doesn't exist. + + @require $defined(Value val = #default_value) +*> +macro Value SortedMap.@get_or_set(&self, Key key, Value #default_value) +{ + if (try v = self.get(key)) return v; + Value val = #default_value; + self.set(key, val); + return val; +} + +<* + @return "true if key exists in the map" +*> +fn bool SortedMap.has_key(&self, Key key) => @ok(self.get(key)); + +<* + @return "true if the key was found and removed" +*> +fn bool SortedMap.remove(&self, Key key) +{ + SkipNode*[MAX_LEVEL] update; + SkipNode* current = self.head; + + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + update[i] = current; + } + + current = current.forward[0]; + + if (!current || sortedmap_cmp(self.custom_cmp, current.key, key) || sortedmap_cmp(self.custom_cmp, key, current.key)) return false; + + for (int i = 0; i < self.current_level; i++) + { + if (update[i].forward[i] != current) break; + update[i].forward[i] = current.forward[i]; + } + + alloc::free(self.allocator, current); + + while (self.current_level > 0 && !self.head.forward[self.current_level - 1]) + { + self.current_level--; + } + + self.count--; + return true; +} + +<* + @return "the first key in iteration order, or NOT_FOUND if empty" +*> +fn Key? SortedMap.first_key(&self) +{ + if (!self.head.forward[0]) return NOT_FOUND~; + return self.head.forward[0].key; +} + +<* + @return "the last key in iteration order, or NOT_FOUND if empty" +*> +fn Key? SortedMap.last_key(&self) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] != null) current = current.forward[i]; + } + if (current == self.head) return NOT_FOUND~; + return current.key; +} + +<* + Remove and return the first entry in iteration order. + @return "the first Entry, or NOT_FOUND if empty" + @return? NOT_FOUND +*> +fn Entry? SortedMap.pop_first(&self) +{ + SkipNode* node = self.head.forward[0]; + if (!node) return NOT_FOUND~; + Entry result = { node.key, node.value }; + self.remove(node.key); + return result; +} + +<* + Remove and return the last entry in iteration order. + @return "the last Entry, or NOT_FOUND if empty" + @return? NOT_FOUND +*> +fn Entry? SortedMap.pop_last(&self) +{ + if (!self.head.forward[0]) return NOT_FOUND~; + Key k = self.last_key()!!; + Value v = self.get(k)!!; + self.remove(k); + return { k, v }; +} + +// --- Floor / ceiling / lower / higher --- +// +// All four operate in the map's iteration (cmp) order. + +<* + @return "the greatest key <= key in cmp order, or NOT_FOUND" + @return? NOT_FOUND +*> +fn Key? SortedMap.floor_key(&self, Key key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + // current is the last node strictly before key; check for exact match + SkipNode* next = current.forward[0]; + if (next && !sortedmap_cmp(self.custom_cmp, next.key, key) && !sortedmap_cmp(self.custom_cmp, key, next.key)) return next.key; + if (current != self.head) return current.key; + return NOT_FOUND~; +} + +<* + @return "the least key >= key in cmp order, or NOT_FOUND" + @return? NOT_FOUND +*> +fn Key? SortedMap.ceiling_key(&self, Key key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + SkipNode* next = current.forward[0]; + if (!next) return NOT_FOUND~; + return next.key; +} + +<* + @return "the greatest key strictly before key in cmp order, or NOT_FOUND" + @return? NOT_FOUND +*> +fn Key? SortedMap.lower_key(&self, Key key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + return current == self.head ? NOT_FOUND~ : current.key; +} + +<* + @return "the least key strictly after key in cmp order, or NOT_FOUND" + @return? NOT_FOUND +*> +fn Key? SortedMap.higher_key(&self, Key key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] != null && sortedmap_cmp(self.custom_cmp, current.forward[i].key, key)) + { + current = current.forward[i]; + } + } + SkipNode* next = current.forward[0]; + if (!next) return NOT_FOUND~; + // If next equals key, advance one more + if (!sortedmap_cmp(self.custom_cmp, next.key, key) && !sortedmap_cmp(self.custom_cmp, key, next.key)) next = next.forward[0]; + return next ? next.key : NOT_FOUND~; +} + +fn sz SortedMap.len(&self) @inline => self.count; + +fn bool SortedMap.is_empty(&self) @inline => !self.count; + +fn void SortedMap.clear(&self) +{ + if (!self.allocator) return; + SkipNode* node = self.head.forward[0]; + while (node) + { + SkipNode* next = node.forward[0]; + alloc::free(self.allocator, node); + node = next; + } + self.head.forward[..self.current_level] = null; + self.count = 0; + self.current_level = 0; +} + +fn void SortedMap.free(&self) +{ + self.clear(); + alloc::free(self.allocator, self.head); + self.head = null; +} + +fn sz? SortedMap.to_format(&self, Formatter* f) @dynamic +{ + sz len; + len += f.print("{ ")!; + SkipNode* node = self.head ? self.head.forward[0] : null; + while (node) + { + if (len > 2) len += f.print(", ")!; + len += f.printf("%s: %s", node.key, node.value)!; + node = node.forward[0]; + } + return len + f.print(" }"); +} + +<* + Copy all entries of this map into a new map backed by allocator. + The clone inherits the custom comparator (if any). +*> +fn SortedMap SortedMap.clone(&self, Allocator allocator) +{ + SortedMap result; + result.init_with_cmp(allocator, self.custom_cmp); + SkipNode* node = self.head.forward[0]; + while (node) + { + result.set(node.key, node.value); + node = node.forward[0]; + } + return result; +} + +fn SortedMap SortedMap.tclone(&self) +{ + return self.clone(tmem) @inline; +} + +<* + Insert all entries from other into self, overwriting on key collision. +*> +fn void SortedMap.merge(&self, SortedMap* other) +{ + SkipNode* node = other.head.forward[0]; + while (node) + { + self.set(node.key, node.value); + node = node.forward[0]; + } +} + +// --- Iteration --- + +<* + Returns an iterator that yields Entry values in sorted key order. + Use with: foreach (entry : m.iter()) { ... } +*> +fn SortedMapIterator SortedMap.iter(&self) +{ + return { .map = self, .current = self.head.forward[0], .index = 0 }; +} + +<* + Returns an iterator starting at the first key >= from_key in cmp order. + Use with: foreach (entry : m.iter_from(key)) { ... } +*> +fn SortedMapRangeIterator SortedMap.iter_from(&self, Key from_key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] != null && sortedmap_cmp(self.custom_cmp, current.forward[i].key, from_key)) + { + current = current.forward[i]; + } + } + SkipNode* start = current.forward[0]; + + sz count = 0; + SkipNode* n = start; + while (n) + { + count++; + n = n.forward[0]; + } + + return { .map = self, .start_node = start, .current = start, .current_idx = 0, .count = count }; +} + +<* + Returns an iterator over keys in [from_key, to_key) in cmp order. + Use with: foreach (entry : m.iter_range(from, to)) { ... } +*> +fn SortedMapRangeIterator SortedMap.iter_range(&self, Key from_key, Key to_key) +{ + SkipNode* current = self.head; + for (int i = self.current_level - 1; i >= 0; i--) + { + while (current.forward[i] && sortedmap_cmp(self.custom_cmp, current.forward[i].key, from_key)) + { + current = current.forward[i]; + } + } + SkipNode* start = current.forward[0]; + + sz count = 0; + SkipNode* n = start; + while (n && sortedmap_cmp(self.custom_cmp, n.key, to_key)) + { + count++; + n = n.forward[0]; + } + + return { .map = self, .start_node = start, .current = start, .current_idx = 0, .count = count }; +} + +<* + @require idx < self.map.count +*> +fn Entry SortedMapIterator.get(&self, sz idx) @operator([]) +{ + if (idx < self.index) + { + self.current = self.map.head.forward[0]; + self.index = 0; + } + while (self.index < idx) + { + self.current = self.current.forward[0]; + self.index++; + } + return { .key = self.current.key, .value = self.current.value }; +} + +fn sz SortedMapIterator.len(&self) @operator(len) => self.map.count; + +<* + @require idx < self.count +*> +fn Entry SortedMapRangeIterator.get(&self, sz idx) @operator([]) +{ + if (idx < self.current_idx) + { + self.current = self.start_node; + self.current_idx = 0; + } + while (self.current_idx < idx) + { + self.current = self.current.forward[0]; + self.current_idx++; + } + return { .key = self.current.key, .value = self.current.value }; +} + +fn sz SortedMapRangeIterator.len(&self) @operator(len) => self.count; diff --git a/test/stdlib/src/collections/tuple.c3 b/test/stdlib/src/collections/tuple.c3 index 061a90c..96c1601 100644 --- a/test/stdlib/src/collections/tuple.c3 +++ b/test/stdlib/src/collections/tuple.c3 @@ -7,7 +7,7 @@ struct Pair (Printable) Type2 second; } -fn usz? Pair.to_format(&self, Formatter* f) @dynamic +fn sz? Pair.to_format(&self, Formatter* f) @dynamic { return f.printf("{ %s, %s }", self.first, self.second); } @@ -39,7 +39,7 @@ struct Triple (Printable) Type3 third; } -fn usz? Triple.to_format(&self, Formatter* f) @dynamic +fn sz? Triple.to_format(&self, Formatter* f) @dynamic { return f.printf("{ %s, %s, %s }", self.first, self.second, self.third); } @@ -49,7 +49,7 @@ fn usz? Triple.to_format(&self, Formatter* f) @dynamic @param [&out] c @require $defined(*a = self.first) : "You cannot assign the first value to a" @require $defined(*b = self.second) : "You cannot assign the second value to b" - @require $defined(*c = self.third) : "You cannot assign the second value to c" + @require $defined(*c = self.third) : "You cannot assign the third value to c" *> macro void Triple.unpack(&self, a, b, c) { @@ -63,10 +63,3 @@ fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Ty return self.first == other.first && self.second == other.second && self.third == other.third; } -module std::collections::tuple ; - -struct Tuple @deprecated("Use 'Pair' instead") -{ - Type1 first; - Type2 second; -} \ No newline at end of file diff --git a/test/stdlib/src/compression/deflate.c3 b/test/stdlib/src/compression/deflate.c3 index 0f859d8..6415e72 100644 --- a/test/stdlib/src/compression/deflate.c3 +++ b/test/stdlib/src/compression/deflate.c3 @@ -33,21 +33,21 @@ struct Inflater (InStream) { StreamBitReader reader; char[65536 + 8] window @align(8); - usz pos; + sz pos; bool final; - uint stored_len; + int stored_len; Huffman* lit_ptr; Huffman* dist_ptr; Huffman dyn_lit; Huffman dyn_dist; Huffman dyn_code_huff; - uint hlit, hdist, hclen; - uint dyn_i; - uint dyn_num_lengths; + int hlit, hdist, hclen; + int dyn_i; + int dyn_num_lengths; char[320] lit_dist_lengths; char[19] code_lengths; - uint match_len; - uint match_dist; + int match_len; + int match_dist; bool done; InflaterState state; } @@ -63,11 +63,11 @@ fn void Inflater.init(&self, InStream input, char[] bit_reader_buf = {}) }); } -fn usz? Inflater.read(&self, char[] buffer) @dynamic +fn sz? Inflater.read(&self, char[] buffer) @dynamic { if (self.done) return 0; - usz total_out = 0; + sz total_out = 0; while (total_out < buffer.len) { switch (self.state) @@ -75,20 +75,20 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic case COPY_MATCH: if (self.match_len > 0) { - uint m_len = self.match_len; - uint m_dist = self.match_dist; + int m_len = self.match_len; + int m_dist = self.match_dist; if (m_dist > self.pos) return CORRUPTED_DATA~; - usz to_copy = math::min((usz)m_len, buffer.len - total_out); - usz s_pos = self.pos; - usz orig_to_copy = to_copy; + sz to_copy = math::min((sz)m_len, buffer.len - total_out); + sz s_pos = self.pos; + sz orig_to_copy = to_copy; if (m_dist >= 16 && to_copy >= 16) { while (to_copy >= 16) { - usz src_idx = (s_pos - m_dist) & 0xFFFF; - usz dst_idx = s_pos & 0xFFFF; + sz src_idx = (s_pos - m_dist) & 0xFFFF; + sz dst_idx = s_pos & 0xFFFF; if (src_idx > 0xFFF0 || dst_idx > 0xFFF0) break; char[16] tmp; @@ -106,8 +106,8 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic { while (to_copy >= 8) { - usz src_idx = (s_pos - m_dist) & 0xFFFF; - usz dst_idx = s_pos & 0xFFFF; + sz src_idx = (s_pos - m_dist) & 0xFFFF; + sz dst_idx = s_pos & 0xFFFF; if (src_idx > 0xFFF8 || dst_idx > 0xFFF8) break; ulong val; @@ -121,7 +121,7 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic } } - for (usz i = 0; i < to_copy; i++) + for (sz i = 0; i < to_copy; i++) { char c = self.window[(s_pos - m_dist) & 0xFFFF]; buffer[total_out++] = c; @@ -130,7 +130,7 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic } self.pos = s_pos; - self.match_len -= (uint)orig_to_copy; + self.match_len -= (int)orig_to_copy; if (self.match_len == 0) { self.state = DECODE_SYMBOL; @@ -160,7 +160,7 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic } else if (symbol <= 285) { - uint len_idx = symbol - 257; + int len_idx = symbol - 257; self.match_len = LENGTH_BASE[len_idx] + self.reader.read_bits(LENGTH_EXTRA[len_idx])!; ushort dist_sym = self.dist_ptr.decode_stream(&self.reader)!; self.match_dist = DIST_BASE[dist_sym] + self.reader.read_bits(DIST_EXTRA[dist_sym])!; @@ -172,9 +172,9 @@ fn usz? Inflater.read(&self, char[] buffer) @dynamic default: if (self.done) break; - usz start = self.pos; + sz start = self.pos; self.step()!; - usz added = self.pos - start; + sz added = self.pos - start; if (added > 0) { buffer[total_out++] = self.window[start & 0xFFFF]; @@ -203,26 +203,27 @@ fn void? Inflater.close(&self) @dynamic fn char[]? decompress(Allocator allocator, char[] input) => @pool() { - if (!input.len) return allocator::new_array(allocator, char, 0); + if (!input.len) return alloc::new_array(allocator, char, 0); ByteReader mem_stream; mem_stream.init(input); - Inflater* inflater = allocator::new(tmem, Inflater); + Inflater* inflater = alloc::new(tmem, Inflater); char[4096] tmp_bit_buf; inflater.init(&mem_stream, &tmp_bit_buf); - usz out_cap = input.len * 2; + sz out_cap = input.len * 2; + if (out_cap < 0) out_cap = input.len; if (out_cap < 1024) out_cap = 1024; ByteWriter writer; writer.tinit(); writer.ensure_capacity(out_cap)!; - usz out_len = io::copy_to(inflater, &writer)!; - if (out_len == 0) return allocator::new_array(allocator, char, 0); + sz out_len = (sz)io::copy_to(inflater, &writer)!; + if (!out_len) return alloc::new_array(allocator, char, 0); - char[] result = allocator::alloc_array(allocator, char, out_len); + char[] result = alloc::alloc_array(allocator, char, out_len); result[..] = writer.array_view()[:out_len]; return result; } @@ -242,19 +243,19 @@ fn void? decompress_stream(InStream input, OutStream output) => @pool() fn void? compress_stream(Allocator allocator, InStream input, OutStream output) { - Deflater* deflater = allocator::new(allocator, Deflater); + Deflater* deflater = alloc::new(allocator, Deflater); deflater.init(allocator, output); defer { (void)deflater.close(); - allocator::free(allocator, deflater); + alloc::free(allocator, deflater); } - char[] buf = allocator::alloc_array(allocator, char, 32768); - defer allocator::free(allocator, buf); + char[] buf = alloc::alloc_array(allocator, char, 32768); + defer alloc::free(allocator, buf); while (true) { - usz? n = input.read(buf[..]); + sz? n = input.read(buf[..]); if (catch err = n) { if (err == io::EOF) break; @@ -276,8 +277,8 @@ struct Deflater (OutStream) uint window_fill; Token[] tokens; - usz token_count; - usz max_tokens; + sz token_count; + sz max_tokens; uint[286] lit_freqs; uint[30] dist_freqs; @@ -287,8 +288,8 @@ struct Deflater (OutStream) uint strstart; uint match_pos; bool hash_ready; - usz total_in; - usz total_out; + sz total_in; + sz total_out; bool closed; } @@ -297,36 +298,37 @@ fn void Deflater.init(&self, Allocator allocator, OutStream output = null) *self = {}; self.allocator = allocator; self.output = output; - self.head = allocator::alloc_array(allocator, uint, HASH_SIZE); + self.head = alloc::alloc_array(allocator, uint, HASH_SIZE); self.head[..] = 0xFFFFFFFF; - self.prev = allocator::alloc_array(allocator, uint, WINDOW_SIZE); - self.window = allocator::alloc_array(allocator, char, WINDOW_SIZE * 2 + MAX_MATCH + 1); + self.prev = alloc::alloc_array(allocator, uint, WINDOW_SIZE); + self.window = alloc::alloc_array(allocator, char, WINDOW_SIZE * 2 + MAX_MATCH + 1); self.window_fill = WINDOW_SIZE; self.strstart = WINDOW_SIZE; self.match_pos = WINDOW_SIZE; self.max_tokens = WINDOW_SIZE * 4; - self.tokens = allocator::alloc_array(allocator, Token, self.max_tokens + 1); + self.tokens = alloc::alloc_array(allocator, Token, self.max_tokens + 1); self.bwriter.init(allocator, WINDOW_SIZE); } fn void Deflater.free(&self) { - if (self.head.ptr) allocator::free(self.allocator, self.head); - if (self.prev.ptr) allocator::free(self.allocator, self.prev); - if (self.window.ptr) allocator::free(self.allocator, self.window); - if (self.tokens.ptr) allocator::free(self.allocator, self.tokens); - if (self.bwriter.data.ptr) allocator::free(self.allocator, self.bwriter.data.ptr); + if (self.head.ptr) alloc::free(self.allocator, self.head); + if (self.prev.ptr) alloc::free(self.allocator, self.prev); + if (self.window.ptr) alloc::free(self.allocator, self.window); + if (self.tokens.ptr) alloc::free(self.allocator, self.tokens); + if (self.bwriter.data.ptr) alloc::free(self.allocator, self.bwriter.data.ptr); } -fn usz? Deflater.write(&self, char[] data) @dynamic +fn sz? Deflater.write(&self, char[] data) @dynamic { if (self.closed) return IO_ERROR~; - usz remaining = data.len; - usz offset = 0; + sz remaining = data.len; + sz offset = 0; while (remaining > 0) { - usz space = (WINDOW_SIZE * 2) - (usz)self.window_fill; + sz space = (WINDOW_SIZE * 2) - (sz)self.window_fill; + assert(space >= 0); if (space == 0) { self.scan_matches(false)!; @@ -336,7 +338,7 @@ fn usz? Deflater.write(&self, char[] data) @dynamic space = WINDOW_SIZE; } - usz to_copy = math::min(remaining, space); + sz to_copy = math::min(remaining, space); mem::copy(self.window[self.window_fill..], data[offset..], to_copy); self.window_fill += (uint)to_copy; remaining -= to_copy; @@ -380,18 +382,18 @@ fn void? Deflater.scan_matches(&self, bool final) uint[] lfreqs = self.lit_freqs[..]; uint[] dfreqs = self.dist_freqs[..]; Token[] tokens = self.tokens; - usz tcount = self.token_count; - usz max_t = self.max_tokens; + sz tcount = self.token_count; + sz max_t = self.max_tokens; uint hash = self.hash; uint strstart = self.strstart; - usz pos = self.match_pos; + long pos = self.match_pos; uint win_offset = strstart - (uint)pos; while (pos < wfill) { - uint best_len = 0; - uint best_dist = 0; + int best_len = 0; + int best_dist = 0; if (pos + 2 < wfill) { @@ -400,19 +402,19 @@ fn void? Deflater.scan_matches(&self, bool final) head[hash] = strstart; prev[strstart & 0x7FFF] = match_head; - uint chain_len = 0; + int chain_len = 0; uint curr = match_head; while (curr != 0xFFFFFFFF && strstart - curr < WINDOW_SIZE && chain_len < MAX_CHAIN) { chain_len++; - usz curr_rel = (usz)(curr - win_offset); + sz curr_rel = (sz)(curr - win_offset); if (pos + best_len < wfill && input[curr_rel + best_len] == input[pos + best_len]) { if (best_len < 3 || mem::load((ushort*)&input[pos + best_len - 1], 1) == mem::load((ushort*)&input[curr_rel + best_len - 1], 1)) { - uint match_l = 0; + int match_l = 0; while (match_l + 8 <= MAX_MATCH && pos + match_l + 8 <= wfill) { if (mem::load((ulong*)&input[curr_rel + match_l], 1) == mem::load((ulong*)&input[pos + match_l], 1)) @@ -432,7 +434,7 @@ fn void? Deflater.scan_matches(&self, bool final) if (match_l >= MIN_MATCH && match_l > best_len) { best_len = match_l; - best_dist = (uint)(pos - curr_rel); + best_dist = (int)(pos - curr_rel); if (best_len >= NICE_MATCH) break; } } @@ -448,7 +450,7 @@ fn void? Deflater.scan_matches(&self, bool final) if (best_len >= MIN_MATCH) { - uint len_code; + int len_code; switch (best_len) { case 3..10: len_code = 257 + best_len - 3; @@ -461,7 +463,7 @@ fn void? Deflater.scan_matches(&self, bool final) } lfreqs[len_code]++; - uint dist_code; + int dist_code; switch (best_dist) { case 1..4: dist_code = best_dist - 1; @@ -482,8 +484,8 @@ fn void? Deflater.scan_matches(&self, bool final) dfreqs[dist_code]++; tokens[tcount++] = { (ushort)best_len, (ushort)best_dist }; - uint limit = best_len - 1; - for (uint k = 0; k < limit; k++) + int limit = best_len - 1; + for (int k = 0; k < limit; k++) { pos++; strstart++; @@ -543,13 +545,13 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() char[] lit_lens = lit_huff.get_lengths(285); char[] dist_lens = dist_huff.get_lengths(29); - usz hlit_cnt = 286; + sz hlit_cnt = 286; while (hlit_cnt > 257 && lit_lens[hlit_cnt - 1] == 0) hlit_cnt--; - usz hdist_cnt = 30; + sz hdist_cnt = 30; while (hdist_cnt > 1 && dist_lens[hdist_cnt - 1] == 0) hdist_cnt--; - self.bwriter.write_bits((uint)hlit_cnt - 257, 5); - self.bwriter.write_bits((uint)hdist_cnt - 1, 5); + self.bwriter.write_bits((uint)hlit_cnt - 257u, 5); + self.bwriter.write_bits((uint)hdist_cnt - 1u, 5); // RLE-encode the combined code lengths List{Token} tree_tokens; tree_tokens.tinit(); @@ -558,25 +560,25 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() all_lens[:hlit_cnt] = lit_lens[:hlit_cnt]; all_lens[hlit_cnt:hdist_cnt] = dist_lens[:hdist_cnt]; - for (usz i = 0; i < all_lens.len;) + for (long i = 0; i < all_lens.len;) { char len = all_lens[i]; - usz count = 1; + long count = 1; while (i + count < all_lens.len && all_lens[i + count] == len) count++; if (len == 0) { while (count >= 11) { - uint c = (uint)math::min(count, (usz)138); - tree_tokens.push({ 18, (ushort)(c - 11) }); + uint c = (uint)math::min(count, (long)138); + tree_tokens.push({ 18, (ushort)(c - 11u) }); tree_freqs[18]++; i += c; count -= c; } while (count >= 3) { - uint c = (uint)math::min(count, (usz)10); - tree_tokens.push({ 17, (ushort)(c - 3) }); + uint c = (uint)math::min(count, (long)10); + tree_tokens.push({ 17, (ushort)(c - 3u) }); tree_freqs[17]++; i += c; count -= c; } @@ -588,8 +590,8 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() i++; count--; while (count >= 3) { - uint c = (uint)math::min(count, (usz)6); - tree_tokens.push({ 16, (ushort)(c - 3) }); + uint c = (uint)math::min(count, (long)6); + tree_tokens.push({ 16, (ushort)(c - 3u) }); tree_freqs[16]++; i += c; count -= c; } @@ -606,10 +608,10 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() Huffman tree_huff; tree_huff.build_from_freqs(tree_freqs[..], 7); char[] tree_lens = tree_huff.get_lengths(18); - usz hclen_cnt = 19; + sz hclen_cnt = 19; while (hclen_cnt > 4 && tree_lens[ORDER[hclen_cnt - 1]] == 0) hclen_cnt--; - self.bwriter.write_bits((uint)hclen_cnt - 4, 4); - for (usz i = 0; i < hclen_cnt; i++) self.bwriter.write_bits(tree_lens[ORDER[i]], 3); + self.bwriter.write_bits((uint)hclen_cnt - 4u, 4); + for (sz i = 0; i < hclen_cnt; i++) self.bwriter.write_bits(tree_lens[ORDER[i]], 3); // Write the RLE-encoded code lengths Code[19] tree_codes; @@ -641,37 +643,37 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() else { uint b_len = t.val; uint b_dist = t.dist; - uint len_code; uint len_extra_bits = 0; uint len_extra = 0; + uint len_code; int len_extra_bits = 0; uint len_extra = 0; switch (b_len) { - case 3..10: len_code = 257 + b_len - 3; - case 11..18: len_code = 265 + (b_len - 11) / 2; len_extra_bits = 1; len_extra = (b_len - 11) % 2; - case 19..34: len_code = 269 + (b_len - 19) / 4; len_extra_bits = 2; len_extra = (b_len - 19) % 4; - case 35..66: len_code = 273 + (b_len - 35) / 8; len_extra_bits = 3; len_extra = (b_len - 35) % 8; - case 67..130: len_code = 277 + (b_len - 67) / 16; len_extra_bits = 4; len_extra = (b_len - 67) % 16; - case 131..257:len_code = 281 + (b_len - 131) / 32; len_extra_bits = 5; len_extra = (b_len - 131) % 32; - default: len_code = 285; + case 3..10: len_code = 257u + b_len - 3u; + case 11..18: len_code = 265u + (b_len - 11u) / 2; len_extra_bits = 1; len_extra = (b_len - 11u) % 2; + case 19..34: len_code = 269u + (b_len - 19u) / 4; len_extra_bits = 2; len_extra = (b_len - 19u) % 4; + case 35..66: len_code = 273u + (b_len - 35u) / 8; len_extra_bits = 3; len_extra = (b_len - 35u) % 8; + case 67..130: len_code = 277u + (b_len - 67u) / 16; len_extra_bits = 4; len_extra = (b_len - 67u) % 16; + case 131..257:len_code = 281u + (b_len - 131u) / 32; len_extra_bits = 5; len_extra = (b_len - 131u) % 32; + default: len_code = 285u; } self.bwriter.write_huffman(lit_codes[len_code].code, lit_codes[len_code].len); if (len_extra_bits > 0) self.bwriter.write_bits(len_extra, len_extra_bits); - uint dist_c; uint dist_extra_b = 0; uint dist_ex = 0; + uint dist_c; int dist_extra_b = 0; uint dist_ex = 0; switch (b_dist) { - case 1..4: dist_c = b_dist - 1; - case 5..8: dist_c = 4 + (b_dist - 5) / 2; dist_extra_b = 1; dist_ex = (b_dist - 5) % 2; - case 9..16: dist_c = 6 + (b_dist - 9) / 4; dist_extra_b = 2; dist_ex = (b_dist - 9) % 4; - case 17..32: dist_c = 8 + (b_dist - 17) / 8; dist_extra_b = 3; dist_ex = (b_dist - 17) % 8; - case 33..64: dist_c = 10 + (b_dist - 33) / 16; dist_extra_b = 4; dist_ex = (b_dist - 33) % 16; - case 65..128: dist_c = 12 + (b_dist - 65) / 32; dist_extra_b = 5; dist_ex = (b_dist - 65) % 32; - case 129..256: dist_c = 14 + (b_dist - 129) / 64; dist_extra_b = 6; dist_ex = (b_dist - 129) % 64; - case 257..512: dist_c = 16 + (b_dist - 257) / 128; dist_extra_b = 7; dist_ex = (b_dist - 257) % 128; - case 513..1024: dist_c = 18 + (b_dist - 513) / 256; dist_extra_b = 8; dist_ex = (b_dist - 513) % 256; - case 1025..2048: dist_c = 20 + (b_dist - 1025) / 512; dist_extra_b = 9; dist_ex = (b_dist - 1025) % 512; - case 2049..4096: dist_c = 22 + (b_dist - 2049) / 1024; dist_extra_b = 10; dist_ex = (b_dist - 2049) % 1024; - case 4097..8192: dist_c = 24 + (b_dist - 4097) / 2048; dist_extra_b = 11; dist_ex = (b_dist - 4097) % 2048; - case 8193..16384: dist_c = 26 + (b_dist - 8193) / 4096; dist_extra_b = 12; dist_ex = (b_dist - 8193) % 4096; - default: dist_c = 28 + (b_dist - 16385) / 8192; dist_extra_b = 13; dist_ex = (b_dist - 16385) % 8192; + case 1..4: dist_c = b_dist - 1u; + case 5..8: dist_c = 4u + (b_dist - 5u) / 2u; dist_extra_b = 1; dist_ex = (b_dist - 5u) % 2; + case 9..16: dist_c = 6u + (b_dist - 9u) / 4u; dist_extra_b = 2; dist_ex = (b_dist - 9u) % 4; + case 17..32: dist_c = 8u + (b_dist - 17u) / 8u; dist_extra_b = 3; dist_ex = (b_dist - 17u) % 8; + case 33..64: dist_c = 10u + (b_dist - 33u) / 16u; dist_extra_b = 4; dist_ex = (b_dist - 33u) % 16; + case 65..128: dist_c = 12u + (b_dist - 65u) / 32u; dist_extra_b = 5; dist_ex = (b_dist - 65u) % 32; + case 129..256: dist_c = 14u + (b_dist - 129u) / 64u; dist_extra_b = 6; dist_ex = (b_dist - 129u) % 64; + case 257..512: dist_c = 16u + (b_dist - 257u) / 128u; dist_extra_b = 7; dist_ex = (b_dist - 257u) % 128; + case 513..1024: dist_c = 18u + (b_dist - 513u) / 256; dist_extra_b = 8; dist_ex = (b_dist - 513u) % 256; + case 1025..2048: dist_c = 20u + (b_dist - 1025u) / 512; dist_extra_b = 9; dist_ex = (b_dist - 1025u) % 512; + case 2049..4096: dist_c = 22u + (b_dist - 2049u) / 1024; dist_extra_b = 10; dist_ex = (b_dist - 2049u) % 1024; + case 4097..8192: dist_c = 24u + (b_dist - 4097u) / 2048; dist_extra_b = 11; dist_ex = (b_dist - 4097u) % 2048; + case 8193..16384: dist_c = 26u + (b_dist - 8193u) / 4096; dist_extra_b = 12; dist_ex = (b_dist - 8193u) % 4096; + default: dist_c = 28u + (b_dist - 16385u) / 8192; dist_extra_b = 13; dist_ex = (b_dist - 16385u) % 8192; } self.bwriter.write_huffman(dist_codes[dist_c].code, dist_codes[dist_c].len); if (dist_extra_b > 0) self.bwriter.write_bits(dist_ex, dist_extra_b); @@ -680,9 +682,9 @@ fn void? Deflater.emit_block(&self, bool final)=> @pool() if (final) { - char[] compressed = self.bwriter.finish(); + char[] compressed = self.bwriter.finish(); if (self.output) self.output.write(compressed)!; - self.total_out += compressed.len; + self.total_out += compressed.len; } else { @@ -744,7 +746,7 @@ fn char[]? compress(Allocator allocator, char[] input) => @pool() deflater.emit_block(true)!; char[] finished = deflater.bwriter.finish(); - char[] result = allocator::alloc_array(allocator, char, finished.len); + char[] result = alloc::alloc_array(allocator, char, finished.len); result[..] = finished[..]; return result; } @@ -769,11 +771,11 @@ struct StreamBitReader @private { InStream stream; char[] buffer; - usz buf_pos; - usz buf_len; + sz buf_pos; + sz buf_len; ulong bit_buf; - uint nbits; - SetCursorFn set_cursor_fn; + int nbits; + SeekFn seek_fn; } enum InflaterState @private @@ -792,33 +794,36 @@ enum InflaterState @private fn void StreamBitReader.init(&self, InStream reader, char[] buffer) { - *self = { .stream = reader, .buffer = buffer, .set_cursor_fn = &reader.set_cursor }; + *self = { .stream = reader, .buffer = buffer, .seek_fn = &reader.seek }; } fn void StreamBitReader.close(&self) { - if (!self.set_cursor_fn) return; + if (!self.seek_fn) return; long seek_back = (long)self.buf_len - (long)self.buf_pos + (long)(self.nbits / 8); if (seek_back > 0) { - (void)self.set_cursor_fn(self.stream, -seek_back, FROM_CURSOR); + (void)self.seek_fn(self.stream, -seek_back, FROM_CURSOR); } + self.buf_len = 0; + self.buf_pos = 0; + self.nbits = 0; } fn void? StreamBitReader.refill(&self) @inline { if (self.buf_pos >= self.buf_len) { - usz n = self.stream.read(self.buffer)!; + sz n = self.stream.read(self.buffer)!; if (!n) return io::EOF~; self.buf_len = n; self.buf_pos = 0; } } -fn uint? StreamBitReader.peek_bits(&self, uint count) @inline +fn uint? StreamBitReader.peek_bits(&self, int count) @inline { - if (count == 0) return 0; + if (!count) return 0u; if (self.nbits < count) { while (self.nbits <= 56) @@ -832,25 +837,25 @@ fn uint? StreamBitReader.peek_bits(&self, uint count) @inline } } if (self.nbits == 0 && count > 0) return CORRUPTED_DATA~; - return (uint)(self.bit_buf & ((1UL << count) - 1)); + return (uint)(self.bit_buf & ((1UL << count) - 1u)); } -fn void StreamBitReader.consume_bits(&self, uint count) @inline +fn void StreamBitReader.consume_bits(&self, int count) @inline { self.bit_buf >>= count; self.nbits -= count; } -fn uint? StreamBitReader.read_bits(&self, uint count) @inline +fn int? StreamBitReader.read_bits(&self, int count) @inline { uint val = self.peek_bits(count)!; self.consume_bits(count); - return val; + return (int)val; } fn void StreamBitReader.align(&self) @inline { - uint skip = self.nbits % 8; + int skip = self.nbits % 8; self.bit_buf >>= skip; self.nbits -= skip; } @@ -858,41 +863,44 @@ fn void StreamBitReader.align(&self) @inline struct BitWriter @private { char[] data; - usz len; + sz len; ulong buffer; - uint nbits; + int nbits; Allocator allocator; } -fn void BitWriter.init(&self, Allocator allocator, usz initial_cap) +<* + @require initial_cap > 0 +*> +fn void BitWriter.init(&self, Allocator allocator, sz initial_cap) { self.allocator = allocator; - self.data = allocator::alloc_array(allocator, char, initial_cap); + self.data = alloc::alloc_array(allocator, char, initial_cap); self.len = 0; self.buffer = 0; self.nbits = 0; } -fn void BitWriter.write_bits(&self, uint value, uint count) +fn void BitWriter.write_bits(&self, uint value, int count) { - self.buffer |= (ulong)(value & ((1 << count) - 1)) << self.nbits; + self.buffer |= (ulong)(value & ((1u << count) - 1u)) << self.nbits; self.nbits += count; - while (self.nbits >= 8) + while (self.nbits >= 8u) { if (self.len >= self.data.len) { - usz new_cap = self.data.len; + sz new_cap = self.data.len; if (new_cap < 1024) new_cap = 1024; while (new_cap <= self.len) { - if (new_cap > usz.max / 2) + if (new_cap > sz::max / 2) { new_cap = self.len + 1; break; } new_cap *= 2; } - self.data = allocator::realloc_array(self.allocator, self.data.ptr, char, new_cap); + self.data = alloc::realloc_array(self.allocator, self.data.ptr, char, new_cap); } self.data[self.len++] = (char)(self.buffer & 0xFF); self.buffer >>= 8; @@ -900,7 +908,7 @@ fn void BitWriter.write_bits(&self, uint value, uint count) } } -fn void BitWriter.write_huffman(&self, uint code, uint len) +fn void BitWriter.write_huffman(&self, uint code, int len) { uint rev = bits::reverse(code << (32 - len)); self.write_bits(rev, len); @@ -922,7 +930,7 @@ struct Huffman @private uint[2048] table; // 11-bit lookup table: (len << 16) | symbol } -fn ushort reverse16(ushort x) @private @inline +fn int reverse16(ushort x) @private @inline { uint res = x; res = ((res & 0xAAAA) >> 1) | ((res & 0x5555) << 1); @@ -938,23 +946,23 @@ fn void Huffman.build(&self, char[] lengths) self.counts = {}; foreach (len : lengths) { if (len > 0 && len < 16) self.counts[len]++; } ushort offset = 0; - for (uint i = 1; i < 16; i++) { offsets[i] = offset; offset += self.counts[i]; } - foreach (uint i, len : lengths) { if (len > 0 && len < 16) { ushort sym_idx = offsets[len]++; if (sym_idx < 288) self.symbols[sym_idx] = (ushort)i; } } + for (int i = 1; i < 16; i++) { offsets[i] = offset; offset += self.counts[i]; } + foreach (int i, len : lengths) { if (len > 0 && len < 16) { ushort sym_idx = offsets[len]++; if (sym_idx < 288) self.symbols[sym_idx] = (ushort)i; } } // Build 11-bit lookup table self.table = {}; uint code = 0; uint sym_idx = 0; - for (uint len = 1; len <= 11; len++) + for (int len = 1; len <= 11; len++) { - uint count = self.counts[len]; - for (uint i = 0; i < count; i++) + int count = self.counts[len]; + for (int i = 0; i < count; i++) { ushort symbol = self.symbols[sym_idx++]; - uint rev_code = reverse16((ushort)code) >> (16 - len); - uint entry = (len << 16) | (uint)symbol; - uint num_entries = 1 << (11 - len); - for (uint j = 0; j < num_entries; j++) + int rev_code = (int)reverse16((ushort)code) >> (16 - len); + uint entry = ((uint)len << 16) | symbol; + int num_entries = 1 << (11 - len); + for (int j = 0; j < num_entries; j++) { self.table[rev_code | (j << len)] = entry; } @@ -972,43 +980,43 @@ struct Token @private struct IndexMap @private { - usz index; + sz index; uint freq; } fn bool IndexMap.less(&self, IndexMap other) => self.freq < other.freq; -fn char[] pkg_merge(Allocator allocator, uint[] freqs, uint max_bits) @private => @pool() +fn char[] pkg_merge(Allocator allocator, uint[] freqs, int max_bits) @private => @pool() { List {IndexMap} map; map.tinit(); - usz n = 0; + sz n = 0; foreach (i, freq : freqs) { if (freq == 0) continue; map.push({i, freq}); n++; } if (n == 0) return {}; sort::quicksort(map.array_view()); if (n == 1) { - char[] blen = allocator::new_array(allocator, char, freqs.len); + char[] blen = alloc::new_array(allocator, char, freqs.len); blen[map[0].index] = 1; return blen; } - usz max_elements = 2 * n; + sz max_elements = 2 * n; uint[] hist = mem::temp_array(uint, max_elements); foreach (i, m : map) hist[i] = m.freq; uint[] current = mem::temp_array(uint, max_elements); uint[] previous = mem::temp_array(uint, max_elements); ulong[] is_merged = mem::temp_array(ulong, max_elements); previous[:n] = hist[:n]; - usz num_previous = n; + sz num_previous = n; is_merged[:max_elements] = 0; - usz num_relevant = 2 * n - 2; + sz num_relevant = 2 * n - 2; ulong mask = 1; - for (uint bits = max_bits - 1; bits > 0; bits--) + for (int bits = max_bits - 1; bits > 0; bits--) { - num_previous &= (usz)~1; + num_previous &= (sz)~1; current[0] = hist[0]; current[1] = hist[1]; uint sum = current[0] + current[1]; - usz num_current = 2; usz num_hist = 2; usz num_merged = 0; + sz num_current = 2; sz num_hist = 2; sz num_merged = 0; while (true) { @@ -1043,7 +1051,7 @@ fn char[] pkg_merge(Allocator allocator, uint[] freqs, uint max_bits) @private = if (num_previous >= num_relevant) { bool keep_going = false; - for (usz i = num_relevant; i > 1; i--) + for (sz i = num_relevant; i > 1; i--) { if (previous[i - 1] != current[i - 1]) { @@ -1057,13 +1065,13 @@ fn char[] pkg_merge(Allocator allocator, uint[] freqs, uint max_bits) @private = } mask >>= 1; hist[..] = 0; - usz num_analyze = num_relevant; + sz num_analyze = num_relevant; while (mask != 0) { - usz num_merged_loop = 0; + sz num_merged_loop = 0; if (max_elements >= 2) { hist[0]++; hist[1]++; } - usz symbol = 2; - for (usz i = symbol; i < num_analyze; i++) + sz symbol = 2; + for (sz i = symbol; i < num_analyze; i++) { if (i < max_elements && (is_merged[i] & mask) == 0) { @@ -1075,20 +1083,20 @@ fn char[] pkg_merge(Allocator allocator, uint[] freqs, uint max_bits) @private = num_analyze = 2 * num_merged_loop; mask >>= 1; } - for (usz i = 0; i < num_analyze; i++) hist[i]++; - char[] blen = allocator::new_array(allocator, char, freqs.len); + for (sz i = 0; i < num_analyze; i++) hist[i]++; + char[] blen = alloc::new_array(allocator, char, freqs.len); foreach (i, m : map) blen[m.index] = (char)hist[i]; return blen; } -fn char[] Huffman.get_lengths(&self, usz max_sym) +fn char[] Huffman.get_lengths(&self, sz max_sym) { - char[] blen = allocator::new_array(tmem, char, max_sym + 1); + char[] blen = alloc::new_array(tmem, char, max_sym + 1); ushort index = 0; - for (uint len = 1; len < 16; len++) + for (int len = 1; len < 16; len++) { - uint count = self.counts[len]; - for (uint i = 0; i < count; i++) + int count = self.counts[len]; + for (int i = 0; i < count; i++) { blen[self.symbols[index++]] = (char)len; } @@ -1096,7 +1104,7 @@ fn char[] Huffman.get_lengths(&self, usz max_sym) return blen; } -fn void Huffman.build_from_freqs(&self, uint[] freqs, uint max_bits) => @pool() +fn void Huffman.build_from_freqs(&self, uint[] freqs, int max_bits) => @pool() { char[] blen = pkg_merge(tmem, freqs, max_bits); self.build(blen); @@ -1106,7 +1114,7 @@ fn void gen_canonical_codes(Code* codes, char[] blen) @private { ushort[16] bl_count; foreach (len : blen) { if (len > 0) bl_count[len]++; } ushort[16] next_code; ushort code = 0; bl_count[0] = 0; - for (uint bits = 1; bits <= 15; bits++) { code = (code + bl_count[bits - 1]) << 1; next_code[bits] = code; } + for (int bits = 1; bits <= 15; bits++) { code = (code + bl_count[bits - 1]) << 1; next_code[bits] = code; } foreach (uint n, len : blen) { if (len != 0) { uint c = next_code[len]; codes[n].code = (ushort)c; codes[n].len = (ushort)len; next_code[len]++; } else { codes[n].code = 0; codes[n].len = 0; } } } @@ -1137,7 +1145,7 @@ fn void gen_fixed_codes(Code* codes) @private ushort[16] bl_count; for (uint i = 0; i < 288; i++) { if (lens[i] > 0) bl_count[lens[i]]++; } ushort[16] next_code; ushort code = 0; bl_count[0] = 0; - for (uint bits = 1; bits <= 15; bits++) { code = (code + bl_count[bits - 1]) << 1; next_code[bits] = code; } + for (int bits = 1; bits <= 15; bits++) { code = (code + bl_count[bits - 1]) << 1; next_code[bits] = code; } for (uint n = 0; n < 288; n++) { uint len = lens[n]; if (len != 0) { uint c = next_code[len]; codes[n].code = (ushort)c; codes[n].len = (ushort)len; next_code[len]++; } else { codes[n].code = 0; codes[n].len = 0; } } } @@ -1147,7 +1155,7 @@ fn ushort? Huffman.decode_stream(&self, StreamBitReader* reader) @inline uint entry = self.table[peek]; if (entry != 0) { - uint len = entry >> 16; + int len = (int)(entry >> 16); if (len <= reader.nbits) { reader.consume_bits(len); @@ -1156,13 +1164,13 @@ fn ushort? Huffman.decode_stream(&self, StreamBitReader* reader) @inline } // Standard walk fallback - uint code = 0; uint first = 0; uint index = 0; - for (uint len = 1; len < 16; len++) + uint code = 0; int first = 0; int index = 0; + for (int len = 1; len < 16; len++) { - uint b = reader.read_bits(1)!; - code |= b; - uint count = self.counts[len]; - if (code < first + count) { return self.symbols[index + (code - first)]; } + int b = reader.read_bits(1)!; + code |= (uint)b; + int count = self.counts[len]; + if (code < first + count) return self.symbols[index + (int)code - first]; index += count; first += count; first <<= 1; code <<= 1; } return CORRUPTED_DATA~; @@ -1190,7 +1198,7 @@ fn void? Inflater.step(&self) case READ_STORED_LEN: self.reader.align(); self.stored_len = self.reader.read_bits(16)!; - uint nlen = self.reader.read_bits(16)!; + int nlen = self.reader.read_bits(16)!; if (self.stored_len != (~nlen & 0xFFFF)) return CORRUPTED_DATA~; if (self.stored_len == 0) { @@ -1237,24 +1245,24 @@ fn void? Inflater.step(&self) self.lit_dist_lengths[self.dyn_i++] = (char)sym; case 16: if (self.dyn_i == 0) return CORRUPTED_DATA~; - uint repeat_count = self.reader.read_bits(2)! + 3; + int repeat_count = self.reader.read_bits(2)! + 3; char prev = self.lit_dist_lengths[self.dyn_i - 1]; if (self.dyn_i + repeat_count > self.dyn_num_lengths || self.dyn_i + repeat_count > 320) return CORRUPTED_DATA~; for (uint k = 0; k < repeat_count; k++) self.lit_dist_lengths[self.dyn_i++] = prev; case 17: - uint zero_len = self.reader.read_bits(3)! + 3; + int zero_len = self.reader.read_bits(3)! + 3; if (self.dyn_i + zero_len > self.dyn_num_lengths || self.dyn_i + zero_len > 320) return CORRUPTED_DATA~; for (uint k = 0; k < zero_len; k++) self.lit_dist_lengths[self.dyn_i++] = 0; case 18: - uint zero_len = self.reader.read_bits(7)! + 11; + int zero_len = self.reader.read_bits(7)! + 11; if (self.dyn_i + zero_len > self.dyn_num_lengths || self.dyn_i + zero_len > 320) return CORRUPTED_DATA~; for (uint k = 0; k < zero_len; k++) self.lit_dist_lengths[self.dyn_i++] = 0; } if (self.dyn_i >= self.dyn_num_lengths) { - uint hlit = self.hlit; - uint hdist = self.hdist; - uint total = hlit + hdist; + int hlit = self.hlit; + int hdist = self.hdist; + int total = hlit + hdist; if (hlit > 286 || hdist > 32 || total > 320 || hlit > total) { return CORRUPTED_DATA~; @@ -1275,7 +1283,7 @@ fn void? Inflater.step(&self) case symbol == 256: self.state = self.final ? DONE : START_BLOCK; case symbol <= 285: - uint len_idx = symbol - 257; + uint len_idx = symbol - 257u; self.match_len = LENGTH_BASE[len_idx] + self.reader.read_bits(LENGTH_EXTRA[len_idx])!; self.state = READ_DIST_SYM; default: @@ -1289,7 +1297,7 @@ fn void? Inflater.step(&self) case COPY_MATCH: if (self.match_dist > self.pos) return CORRUPTED_DATA~; - char c = self.window[(usz)((self.pos - self.match_dist) & 0xFFFF)]; + char c = self.window[(sz)((self.pos - self.match_dist) & 0xFFFF)]; self.write_byte(c); self.match_len--; if (self.match_len == 0) @@ -1304,7 +1312,7 @@ fn void? Inflater.step(&self) return; } -fn void Inflater.write_byte(&self, char c) @inline { self.window[(usz)(self.pos & 0xFFFF)] = c; self.pos++; } +fn void Inflater.write_byte(&self, char c) @inline { self.window[(sz)(self.pos & 0xFFFF)] = c; self.pos++; } const ushort[31] LENGTH_BASE @private = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; const char[31] LENGTH_EXTRA @private = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; diff --git a/test/stdlib/src/compression/gzip.c3 b/test/stdlib/src/compression/gzip.c3 index ed4fa91..f3a1970 100644 --- a/test/stdlib/src/compression/gzip.c3 +++ b/test/stdlib/src/compression/gzip.c3 @@ -1,9 +1,22 @@ -// Copyright (c) 2026 Koni Marti. All rights reserved. -// Use of this source code is governed by the MIT license. +<* + Gzip compression and decompression module (DEFLATE). + + API: + - fn char[]? compress(Allocator allocator, char[] src, ...) + - fn void? compress_stream(OutStream writer, char[] src, ...) + - fn Gzip? uncompress(Allocator allocator, char[] src) + - fn char[]? uncompress_bytes(Allocator allocator, char[] src) + - fn void? uncompress_stream(OutStream writer, InStream src) + - struct GzipWriter + - struct GzipReader + + Initial implementation by Koni Marti. + Streaming and concatenation support added by Manu Linares. +*> module std::compression::gzip; -import std::io, std::hash::crc32, std::compression::deflate; +import std::io, std::hash::crc32, std::compression::deflate, std::encoding::codepage; -faultdef CORRUPT_HEADER, CORRUPT_DATA; +faultdef CORRUPT_HEADER, CORRUPT_DATA, UNSUPPORTED_METHOD, IO_ERROR; struct GzipHeader @packed { @@ -23,6 +36,10 @@ struct GzipHeader @packed char os; } +const char GZIP_ID1 = 0x1F; +const char GZIP_ID2 = 0x8B; +const char GZIP_CM_DEFLATE = 8; + struct Gzip { Allocator allocator; @@ -36,180 +53,346 @@ struct Gzip fn void Gzip.free(&self) { if (!self.allocator) return; - allocator::free(self.allocator, self.extra); + alloc::free(self.allocator, self.extra); self.filename.free(self.allocator); self.comment.free(self.allocator); - allocator::free(self.allocator, self.uncompressed); + alloc::free(self.allocator, self.uncompressed); } -fn char[]? uncompress_bytes(Allocator allocator, char[] src) @inline => @pool() +fn char[]? compress(Allocator allocator, char[] src, bool hcrc = false, + char[] extra = {}, String name = "", String comment = "") { - Gzip gz = uncompress_stream(tmem, &&io::wrap_bytes(src))!; - return allocator::clone_slice(allocator, gz.uncompressed); + ByteWriter bw; + bw.init(allocator); + compress_stream(&bw, src, hcrc, extra, name, comment)!; + return bw.array_view(); } -fn Gzip? uncompress(Allocator allocator, char[] src) @inline +fn void? compress_stream(OutStream writer, char[] src, bool hcrc = false, char[] extra = {}, String name = "", String comment = "") { - return uncompress_stream(allocator, &&io::wrap_bytes(src)); + GzipWriter gz; + gz.init(tmem, writer, hcrc, extra, name, comment)!; + gz.write(src)!; + gz.close()!; } -fn Gzip? uncompress_stream(Allocator allocator, InStream reader) +fn Gzip? uncompress(Allocator allocator, char[] src) { - Gzip gz = { .allocator = allocator }; - defer catch gz.free(); - - @pool() - { - CrcWriter crc; - crc.init(); - - InStream header_reader = (TeeReader){}.init(reader, &crc); + ByteReader br = io::wrap_bytes(src); + GzipReader reader; + reader.init(&br, allocator)!; + defer (void)reader.close(); - header_reader.read((char[GzipHeader.sizeof]*)&gz.header)!; - - if (gz.header.id != GZIP_MAGIC) return CORRUPT_HEADER~; - if (gz.header.cm != 8) return CORRUPT_HEADER~; - if (gz.header.flg.reserved) return CORRUPT_HEADER~; - - if (gz.header.flg.extra) - { - ushort xlen = io::read_le_ushort(header_reader)!; - gz.extra = allocator::malloc(allocator, xlen)[:xlen]; - header_reader.read(gz.extra)!; - } - - if (gz.header.flg.name) - { - gz.filename = decode_latin1(allocator, header_reader)!; - } + ByteWriter bw; + bw.tinit(); - if (gz.header.flg.comment) - { - gz.comment = decode_latin1(allocator, header_reader)!; - } + do_uncompress(&reader, &bw)!; - if (gz.header.flg.hcrc) - { - ushort crc16 = (ushort)(crc.final() & 0xFFFF); - ushort hcrc = io::read_le_ushort(reader)!; - if (crc16 != hcrc) return CORRUPT_HEADER~; - } + Gzip gz = { + .allocator = allocator, + .header = reader.metadata.header, + .extra = alloc::clone_slice(allocator, reader.metadata.extra), + .filename = reader.metadata.filename.copy(allocator), + .comment = reader.metadata.comment.copy(allocator), + .uncompressed = alloc::clone_slice(allocator, bw.array_view()) }; - - ByteWriter uncomp; - uncomp.init(allocator); - deflate::decompress_stream(reader, &uncomp)!; - gz.uncompressed = uncomp.array_view(); - - uint crc32 = io::read_le_uint(reader)!; - if (crc32 != crc32::hash(gz.uncompressed)) return CORRUPT_DATA~; - - uint isize = io::read_le_uint(reader)!; - if (isize != (gz.uncompressed.len & 0xFFFFFFFF)) return CORRUPT_DATA~; - return gz; } -fn char[]? compress(Allocator allocator, char[] src, bool hcrc = false, - char[] extra = {}, String name = "", String comment = "") +fn char[]? uncompress_bytes(Allocator allocator, char[] src) { + ByteReader br = io::wrap_bytes(src); ByteWriter bw; bw.init(allocator); - compress_stream(&bw, src, hcrc, extra, name, comment)!; + uncompress_stream(&bw, &br)!; return bw.array_view(); } -fn void? compress_stream(OutStream writer, char[] src, bool hcrc = false, char[] extra = {}, String name = "", String comment = "") +fn void? uncompress_stream(OutStream writer, InStream src) { + GzipReader reader; + reader.init(src, tmem)!; + defer (void)reader.close(); + do_uncompress(&reader, writer)!; +} + +fn void? do_uncompress(GzipReader* reader, OutStream writer) @private +{ + char[32768] buf; + while (true) + { + sz n = reader.read(buf[..])!; + if (n == 0) break; + writer.write(buf[:n])!; + } +} + + +struct GzipWriter (OutStream) +{ + OutStream next; + deflate::Deflater deflater; + crc32::Crc32 hasher; + usz total_in; +} + +fn void? GzipWriter.init(&self, Allocator allocator, OutStream writer, bool hcrc = false, + char[] extra = {}, String name = "", String comment = "") +{ + self.next = writer; + self.hasher.init(); + self.total_in = 0; + GzipHeader header = { - .id = GZIP_MAGIC, + .id = { GZIP_ID1, GZIP_ID2 }, .flg = { .hcrc = hcrc, .extra = extra.len > 0, .name = name.len > 0, .comment = comment.len > 0 }, - .cm = 8, + .cm = GZIP_CM_DEFLATE, .os = 0xff, }; - @pool() - { - ByteWriter h; - h.tinit(); - - h.write((char[GzipHeader.sizeof]*)&header)!; - if (extra.len) - { - io::write_le_short(&h, (ushort)extra.len)!; - h.write(extra)!; - } - if (name.len) - { - encode_latin1(&h, name)!; - } - if (comment.len) - { - encode_latin1(&h, comment)!; - } - if (hcrc) - { - uint crc_hash = crc32::hash(h.array_view()); - io::write_le_short(&h, (ushort)(crc_hash & 0xFF_FF))!; - } + ByteWriter h; + h.tinit(); - writer.write(h.array_view())!; + h.write(((char*)&header)[:GzipHeader::size])!; + if (extra.len) + { + io::write_le_ushort(&h, (ushort)extra.len)!; + h.write(extra)!; + } + if (name.len) encode_latin1(&h, name)!; + if (comment.len) encode_latin1(&h, comment)!; + if (hcrc) + { + uint crc_hash = crc32::hash(h.array_view()); + io::write_le_ushort(&h, (ushort)(crc_hash & 0xFF_FF))!; + } + self.next.write(h.array_view())!; - deflate::compress_stream(tmem, &&io::wrap_bytes(src), writer)!; - }; + self.deflater.init(allocator, self.next); +} - io::write_le_int(writer, crc32::hash(src))!; - io::write_le_int(writer, src.len & 0xFF_FF_FF_FF)!; +fn sz? GzipWriter.write(&self, char[] bytes) @dynamic +{ + if (!bytes.len) return 0; + self.hasher.update(bytes); + self.total_in += (usz)bytes.len; + return self.deflater.write(bytes); } +fn void? GzipWriter.write_byte(&self, char c) @dynamic +{ + self.hasher.update_byte(c); + self.total_in++; + return self.deflater.write_byte(c); +} -module std::compression::gzip @private; -import std::io, std::encoding::codepage, std::hash::crc32; +fn void? GzipWriter.close(&self) @dynamic +{ + self.deflater.close()!; + io::write_le_uint(self.next, self.hasher.final())!; + io::write_le_uint(self.next, (uint)(self.total_in & 0xFFFFFFFF))!; +} -const char[2] GZIP_MAGIC = x'1F8B'; +struct GzipMetadata +{ + GzipHeader header; + char[] extra; + String filename; + String comment; +} -struct CrcWriter (OutStream) +struct GzipReader (InStream) { - Crc32 hash; + InStream source; + deflate::Inflater* inflater; + crc32::Crc32 hasher; + usz total_out; + bool header_read; + bool footer_verified; + GzipMetadata metadata; + char* bit_buf; + Allocator allocator; } -fn void CrcWriter.init(&self) +fn void? GzipReader.init(&self, InStream reader, Allocator allocator = tmem) { - self.hash.init(); + self.source = reader; + self.allocator = allocator; + self.hasher.init(); + self.total_out = 0; + self.header_read = false; + self.footer_verified = false; + self.metadata = {}; + self.inflater = null; } -fn usz? CrcWriter.write(&self, char[] bytes) @dynamic +fn void GzipReader.free_metadata(&self, Allocator allocator) { - self.hash.update(bytes); - return bytes.len; + if (self.metadata.extra.ptr) alloc::free(allocator, self.metadata.extra); + if (self.metadata.filename.len > 0) self.metadata.filename.free(allocator); + if (self.metadata.comment.len > 0) self.metadata.comment.free(allocator); + self.metadata.extra = {}; + self.metadata.filename = ""; + self.metadata.comment = ""; } -fn void? CrcWriter.write_byte(&self, char c) @dynamic +fn sz? GzipReader.read(&self, char[] bytes) @dynamic { - self.hash.updatec(c); + if (self.footer_verified) return 0; + + if (!self.header_read) + { + self.read_header()!; + self.header_read = true; + + self.bit_buf = alloc::alloc_array(self.allocator, char, 8192); + self.inflater = alloc::new(self.allocator, deflate::Inflater); + self.inflater.init(self.source, self.bit_buf[:8192]); + } + + sz? n_res = self.inflater.read(bytes); + if (catch err = n_res) + { + return err~; + } + + sz n = n_res; + if (n > 0) + { + self.hasher.update(bytes[:n]); + self.total_out += (usz)n; + return n; + } + + if (!self.footer_verified) + { + self.inflater.close()!; + + if (try self.source.seek(0, FROM_CURSOR)) + { + uint crc_stored = io::read_le_uint(self.source)!; + uint isize_stored = io::read_le_uint(self.source)!; + + if (crc_stored != self.hasher.final()) return CORRUPT_DATA~; + if (isize_stored != (uint)(self.total_out & uint::max)) return CORRUPT_DATA~; + } + + self.footer_verified = true; + + alloc::free(self.allocator, self.bit_buf); + alloc::free(self.allocator, self.inflater); + self.inflater = null; + self.bit_buf = null; + } + + char[2] magic; + sz? next_magic = self.source.read(magic[..]); + if (try nm = next_magic && nm == 2) + { + if (magic[0] == GZIP_ID1 && magic[1] == GZIP_ID2) + { + self.header_read = false; + self.footer_verified = false; + self.hasher.init(); + self.total_out = 0; + self.free_metadata(self.allocator); + + self.source.seek(-2, FROM_CURSOR)!; + return self.read(bytes); + } + self.source.seek(-2, FROM_CURSOR)!; + } + + return 0; } -fn uint CrcWriter.final(&self) => self.hash.final(); +fn void? GzipReader.read_header(&self) @dynamic +{ + ByteWriter h; + h.tinit(); -<* - Read a zero-terminated Latin1 string from reader. -*> -fn String? decode_latin1(Allocator allocator, InStream reader) => @pool() + char[GzipHeader::size] h_bytes; + sz n = self.source.read(h_bytes[..])!; + if (n != GzipHeader::size) return IO_ERROR~; + h.write(h_bytes[..])!; + + self.metadata.header = *(GzipHeader*)&h_bytes; + + if (self.metadata.header.id[0] != GZIP_ID1 || self.metadata.header.id[1] != GZIP_ID2) return CORRUPT_HEADER~; + if (self.metadata.header.cm != GZIP_CM_DEFLATE) return UNSUPPORTED_METHOD~; + if (self.metadata.header.flg.reserved) return CORRUPT_HEADER~; + + if (self.metadata.header.flg.extra) + { + ushort xlen = io::read_le_ushort(self.source)!; + io::write_le_ushort(&h, xlen)!; + + self.metadata.extra = alloc::alloc_array(self.allocator, char, xlen); + self.source.read(self.metadata.extra)!; + h.write(self.metadata.extra)!; + } + + if (self.metadata.header.flg.name) + { + ByteWriter name_bw; + name_bw.tinit(); + while (true) + { + char c = self.source.read_byte()!; + h.write_byte(c)!; + if (c == '\0') break; + name_bw.write_byte(c)!; + } + self.metadata.filename = codepage::decode(self.allocator, name_bw.array_view(), CodePage.ISO_8859_1)!; + } + + if (self.metadata.header.flg.comment) + { + ByteWriter comment_bw; + comment_bw.tinit(); + while (true) + { + char c = self.source.read_byte()!; + h.write_byte(c)!; + if (c == '\0') break; + comment_bw.write_byte(c)!; + } + self.metadata.comment = codepage::decode(self.allocator, comment_bw.array_view(), CodePage.ISO_8859_1)!; + } + + if (self.metadata.header.flg.hcrc) + { + uint crc_hash = crc32::hash(h.array_view()); + ushort crc16 = (ushort)(crc_hash & 0xFFFF); + ushort hcrc = io::read_le_ushort(self.source)!; + if (crc16 != hcrc) return CORRUPT_HEADER~; + } +} + +fn void? GzipReader.close(&self) @dynamic { - ByteWriter writer; - writer.tinit(); - while (try c = reader.read_byte() && c) + self.free_metadata(self.allocator); + if (self.inflater) { - writer.write_byte(c)!; + alloc::free(self.allocator, self.bit_buf); + alloc::free(self.allocator, self.inflater); + self.inflater = null; } - return codepage::decode(allocator, writer.array_view(), CodePage.ISO_8859_1)!; + return; } -<* - Write a string s as a zero-terminated Latin1 buffer to writer. -*> -fn void? encode_latin1(OutStream writer, String s) => @pool() +fn char? GzipReader.read_byte(&self) @dynamic { - writer.write(codepage::encode(tmem, s, CodePage.ISO_8859_1)!)!; - writer.write_byte('\0')!; + char[1] b; + sz n = self.read(b[..])!; + if (n == 0) return io::EOF~; + return b[0]; +} + +fn void? encode_latin1(OutStream writer, String s) +{ + @pool() { + writer.write(codepage::encode(tmem, s, CodePage.ISO_8859_1)!)!; + writer.write_byte('\0')!; + }; } diff --git a/test/stdlib/src/compression/qoi.c3 b/test/stdlib/src/compression/qoi.c3 index dc6f57e..46dae26 100644 --- a/test/stdlib/src/compression/qoi.c3 +++ b/test/stdlib/src/compression/qoi.c3 @@ -1,6 +1,6 @@ module std::compression::qoi; -const uint PIXELS_MAX = 400000000; +const uint PIXELS_MAX = 400_000_000; <* Colorspace. @@ -65,7 +65,7 @@ import std::io; @param [in] input : `The raw RGB or RGBA pixels to encode` @param [&in] desc : `The descriptor of the image` *> -fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool() +fn sz? write(String filename, char[] input, QOIDesc* desc) => @pool() { // encode data char[] output = encode(tmem, input, desc)!; @@ -139,9 +139,9 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard // allocate memory for encoded data (output) // header + chunk tag and RGB(A) data for each pixel + end of stream - uint max_size = Header.sizeof + pixels + image_size + END_OF_STREAM.len; - char[] output = allocator::alloc_array(allocator, char, max_size); // no need to init - defer catch allocator::free(allocator, output); + sz max_size = Header::size + (sz)pixels + (sz)image_size + END_OF_STREAM.len; + char[] output = alloc::alloc_array(allocator, char, max_size); // no need to init + defer catch alloc::free(allocator, output); // write header *(Header*)output.ptr = { @@ -152,7 +152,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard .colorspace = desc.colorspace }; - uint pos = Header.sizeof; // Current position in output + uint pos = Header::size; // Current position in output uint loc; // Current position in image (top-left corner) uint loc_end = image_size - desc.channels; // End of image data char run_length = 0; // Length of the current run @@ -172,7 +172,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard // get current pixel p[:3] = input[loc:3]; // cutesy slices :3 - if (desc.channels == RGBA) p.a = input[loc + 3]; + if (desc.channels == RGBA) p.a = input[loc + 3u]; // check if we can run the previous pixel if (prev == p) @@ -180,7 +180,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard run_length++; if (run_length == 62 || loc == loc_end) { - *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 }; + *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1u }; run_length = 0; } continue; @@ -188,7 +188,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard // end last run if there was one if (run_length > 0) { - *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 }; + *@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1u }; run_length = 0; } @@ -204,16 +204,16 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard // check if we can use diff or luma case (prev != p && prev.a == p.a): // diff the pixels - diff = p.rgb - prev.rgb; + diff = (ichar[<3>])(p.rgb - prev.rgb); if (diff.r > -3 && diff.r < 2 && diff.g > -3 && diff.g < 2 && diff.b > -3 && diff.b < 2) { *@extract(OpDiff, output, &pos) = { OP_DIFF, - (char)diff.r + 2, - (char)diff.g + 2, - (char)diff.b + 2 + (char)diff.r + 2u, + (char)diff.g + 2u, + (char)diff.b + 2u }; palette[p.hash()] = p; break; @@ -226,9 +226,9 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard { *@extract(OpLuma, output, &pos) = { OP_LUMA, - (char)luma.g + 32, - (char)luma.r + 8, - (char)luma.b + 8 + (char)luma.g + 32u, + (char)luma.r + 8u, + (char)luma.b + 8u }; palette[p.hash()] = p; break; @@ -283,7 +283,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard { // check input data - if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA~; + if (data.len < Header::size + END_OF_STREAM.len) return INVALID_DATA~; // get header Header* header = (Header*)data.ptr; @@ -305,7 +305,7 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c ulong pixels = (ulong)width * (ulong)height; if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~; - uint pos = Header.sizeof; // Current position in data + uint pos = Header::size; // Current position in data uint loc; // Current position in image (top-left corner) char run_length = 0; // Length of the current run char tag; // Current chunk tag @@ -316,9 +316,9 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c if (channels == AUTO) channels = desc_channels; // allocate memory for image data - usz image_size = (usz)pixels * channels; - char[] image = allocator::alloc_array(allocator, char, image_size); - defer catch allocator::free(allocator, image); + sz image_size = (sz)pixels * channels; + char[] image = alloc::alloc_array(allocator, char, image_size); + defer catch alloc::free(allocator, image); for (loc = 0; loc < image_size; loc += channels) { @@ -347,17 +347,17 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c case tag >> 6 == OP_DIFF: OpDiff* op = @extract(OpDiff, data, &pos); - p.r += op.diff_red - 2; - p.g += op.diff_green - 2; - p.b += op.diff_blue - 2; + p.r += op.diff_red - 2u; + p.g += op.diff_green - 2u; + p.b += op.diff_blue - 2u; palette[p.hash()] = p; case tag >> 6 == OP_LUMA: OpLuma* op = @extract(OpLuma, data, &pos); - int diff_green = op.diff_green - 32; - p.r += (char)(op.diff_red_minus_green - 8 + diff_green); + int diff_green = (int)op.diff_green - 32; + p.r += (char)((int)op.diff_red_minus_green - 8 + diff_green); p.g += (char)(diff_green); - p.b += (char)(op.diff_blue_minus_green - 8 + diff_green); + p.b += (char)((int)op.diff_blue_minus_green - 8 + diff_green); palette[p.hash()] = p; case tag >> 6 == OP_RUN: @@ -412,7 +412,7 @@ const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1}; typedef Pixel = inline char[<4>]; macro char Pixel.hash(Pixel p) { - return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64; + return (p.r * 3u + p.g * 5u + p.b * 7u + p.a * 11u) % 64u; } struct OpRGB // No need to use @packed here, the alignment is 1 anyways. @@ -460,7 +460,7 @@ bitstruct OpRun : char macro @extract($Type, char[] data, uint* pos) { // slice data, then double cast - $Type* chunk = ($Type*)data[*pos : $Type.sizeof].ptr; - *pos += $Type.sizeof; + $Type* chunk = ($Type*)data[*pos : $Type::size].ptr; + *pos += $Type::size; return chunk; } diff --git a/test/stdlib/src/compression/zip.c3 b/test/stdlib/src/compression/zip.c3 index 3820591..64db9b2 100644 --- a/test/stdlib/src/compression/zip.c3 +++ b/test/stdlib/src/compression/zip.c3 @@ -12,6 +12,7 @@ module std::compression::zip; import std::io, std::collections::list, std::hash::crc32, std::time, std::math; import std::encoding::codepage, std::compression::deflate; +import std::crypto::aes, std::hash::sha1; import libc; faultdef @@ -20,6 +21,8 @@ faultdef CORRUPTED_DATA, ENTRY_NOT_FOUND, ENCRYPTED_FILE, + PASSWORD_NEEDED, + PASSWORD_MISMATCH, UNSUPPORTED_METHOD; <* @@ -28,12 +31,12 @@ faultdef struct ZipEntry { String name; - ulong uncompressed_size; - ulong compressed_size; + long uncompressed_size; + long compressed_size; bool is_directory; bool is_encrypted; uint crc32; - ulong offset; + long offset; ZipMethod method; ushort last_mod_time; ushort last_mod_date; @@ -57,6 +60,7 @@ constdef ZipMethod : UShortLE { STORE = {0}, DEFLATE = {8}, + AEX = {99}, } <* @@ -75,7 +79,7 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") ZipArchive archive; archive.allocator = allocator; - archive.file = allocator::new(allocator, File, f); + archive.file = alloc::new(allocator, File, f); archive.entries.init(allocator); archive.path = path.copy(allocator); archive.mode = mode.copy(allocator); @@ -86,30 +90,28 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") File f = file::open(path, "rb")!; defer (catch err) (void)f.close(); - ulong file_size = f.size()!!; + long file_size = f.size()!!; - if (file_size < ZipEOCD.sizeof) return CORRUPTED_DATA~; + if (file_size < ZipEOCD::size) return CORRUPTED_DATA~; - ulong search_start = file_size > (ulong)(ZipEOCD.sizeof + 65535) - ? file_size - (ulong)(ZipEOCD.sizeof + 65535) - : 0; + long search_start = file_size > ZipEOCD::size + 65535LL ? file_size - (ZipEOCD::size + 65535LL) : 0; ZipEOCD eocd; bool found = false; - for (ulong pos = file_size - (ulong)ZipEOCD.sizeof; pos >= search_start; pos--) + for (long pos = file_size - ZipEOCD::size; pos >= search_start; pos--) { - if (pos > (ulong)isz.max) return io::OVERFLOW~; - f.set_cursor(pos)!; + if (pos > sz::max) return io::OVERFLOW~; + f.seek(pos)!; UIntLE sig; if (io::read_any(&f, &sig)! != 4) break; if (sig.val == ZIP_EOCD_SIG) { - f.set_cursor(pos)!; - if (io::read_any(&f, &eocd)! == ZipEOCD.sizeof) + f.seek(pos)!; + if (io::read_any(&f, &eocd)! == ZipEOCD::size) { - ulong expected_end = pos + ZipEOCD.sizeof + eocd.comment_len.val; + long expected_end = pos + ZipEOCD::size + eocd.comment_len.val; if (expected_end == file_size) { found = true; @@ -124,16 +126,16 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") ZipArchive archive; archive.allocator = allocator; - archive.file = allocator::new(allocator, File, f); + archive.file = alloc::new(allocator, File, f); archive.entries.init(allocator); archive.path = path.copy(allocator); archive.mode = mode.copy(allocator); if (eocd.comment_len.val > 0) { - char[] comment_data = allocator::alloc_array(allocator, char, (usz)eocd.comment_len.val); - defer allocator::free(allocator, comment_data); - if (archive.file.read(comment_data)! == (usz)eocd.comment_len.val) + char[] comment_data = alloc::alloc_array(allocator, char, (sz)eocd.comment_len.val); + defer alloc::free(allocator, comment_data); + if (archive.file.read(comment_data)! == (sz)eocd.comment_len.val) { archive.comment = codepage::decode(allocator, comment_data, CP437)!!; } @@ -141,49 +143,49 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") defer catch (void)archive.close(); - if (eocd.cd_offset.val > (uint)isz.max) return io::OVERFLOW~; - archive.file.set_cursor(eocd.cd_offset.val)!; + if (eocd.cd_offset.val > (uint)sz::max) return io::OVERFLOW~; + archive.file.seek(eocd.cd_offset.val)!; - usz num_entries = eocd.num_entries.val; + sz num_entries = eocd.num_entries.val; // ZIP64 check if (eocd.num_entries.val == 0xFFFF || eocd.cd_offset.val == 0xFFFFFFFF) { - isz locator_pos = (isz)file_size - ZipEOCD.sizeof - Zip64Locator.sizeof; + sz locator_pos = (sz)file_size - ZipEOCD::size - Zip64Locator::size; if (locator_pos >= 0) { - archive.file.set_cursor(locator_pos)!; + archive.file.seek(locator_pos)!; Zip64Locator locator; - if (try n = archive.file.read(((char*)&locator)[:Zip64Locator.sizeof])) + if (try n = archive.file.read(((char*)&locator)[:Zip64Locator::size])) { - if (n == Zip64Locator.sizeof && locator.signature.val == ZIP64_LOCATOR_SIG) + if (n == Zip64Locator::size && locator.signature.val == ZIP64_LOCATOR_SIG) { - if (locator.offset_eocd.val > (ulong)isz.max) return io::OVERFLOW~; - archive.file.set_cursor(locator.offset_eocd.val)!; + if (locator.offset_eocd.val > (ulong)sz::max) return io::OVERFLOW~; + archive.file.seek(locator.offset_eocd.val)!; Zip64EOCD eocd64; io::read_any(archive.file, &eocd64)!; if (eocd64.signature.val == ZIP64_EOCD_SIG) { - if (eocd64.offset_cd.val > (ulong)isz.max) return io::OVERFLOW~; - archive.file.set_cursor(eocd64.offset_cd.val)!!; - num_entries = (usz)eocd64.count_total.val; + if (eocd64.offset_cd.val > (ulong)sz::max) return io::OVERFLOW~; + archive.file.seek(eocd64.offset_cd.val)!!; + num_entries = (sz)eocd64.count_total.val; } } } } } - for (usz i = 0; i < num_entries; i++) + for (sz i = 0; i < num_entries; i++) { ZipCDH cdh; - if (io::read_any(archive.file, &cdh)! != ZipCDH.sizeof) break; + if (io::read_any(archive.file, &cdh)! != ZipCDH::size) break; if (cdh.signature.val != ZIP_CDH_SIG) break; - char[] raw_name = allocator::alloc_array(allocator, char, cdh.filename_len.val); - if (archive.file.read(raw_name)! != (usz)cdh.filename_len.val) + char[] raw_name = alloc::alloc_array(allocator, char, cdh.filename_len.val); + if (archive.file.read(raw_name)! != (sz)cdh.filename_len.val) { - allocator::free(allocator, raw_name); + alloc::free(allocator, raw_name); break; } @@ -196,21 +198,21 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") else { name = (String)codepage::decode(allocator, raw_name, CP437)!!; - allocator::free(allocator, raw_name.ptr); + alloc::free(allocator, raw_name.ptr); } char[] extra_field; if (cdh.extra_field_len.val > 0) { - extra_field = allocator::alloc_array(allocator, char, cdh.extra_field_len.val); + extra_field = alloc::alloc_array(allocator, char, cdh.extra_field_len.val); archive.file.read(extra_field)!; } - archive.file.set_cursor(cdh.comment_len.val, FROM_CURSOR)!; + archive.file.seek(cdh.comment_len.val, FROM_CURSOR)!; - ulong uncompressed_size = cdh.uncompressed_size.val; - ulong compressed_size = cdh.compressed_size.val; - ulong offset = cdh.relative_offset.val; + long uncompressed_size = cdh.uncompressed_size.val; + long compressed_size = cdh.compressed_size.val; + long offset = cdh.relative_offset.val; if (cdh.uncompressed_size.val == 0xFFFFFFFF || cdh.compressed_size.val == 0xFFFFFFFF || cdh.relative_offset.val == 0xFFFFFFFF) { @@ -225,25 +227,25 @@ fn ZipArchive? open(Allocator allocator, String path, String mode = "r") int remaining = size; if (cdh.uncompressed_size.val == 0xFFFFFFFF && remaining >= 8) { - uncompressed_size = io::read_le_ulong(&reader)!; + uncompressed_size = io::read_le_long(&reader)!; remaining -= 8; } if (cdh.compressed_size.val == 0xFFFFFFFF && remaining >= 8) { - compressed_size = io::read_le_ulong(&reader)!; + compressed_size = io::read_le_long(&reader)!; remaining -= 8; } if (cdh.relative_offset.val == 0xFFFFFFFF && remaining >= 8) { - offset = io::read_le_ulong(&reader)!; + offset = io::read_le_long(&reader)!; remaining -= 8; } break; } - reader.set_cursor(size, FROM_CURSOR)!; + reader.seek(size, FROM_CURSOR)!; } } - if (extra_field.len > 0) allocator::free(allocator, extra_field); + if (extra_field.len > 0) alloc::free(allocator, extra_field); bool is_directory = name.ends_with("/") || name.ends_with("\\"); if (!is_directory) @@ -292,7 +294,7 @@ fn ZipArchive? recover(Allocator allocator, String path) ZipArchive archive = { .allocator = allocator, - .file = allocator::new(allocator, File, f), + .file = alloc::new(allocator, File, f), .path = path.copy(allocator), .mode = "r".copy(allocator) }; @@ -303,23 +305,23 @@ fn ZipArchive? recover(Allocator allocator, String path) while (true) { long offset = archive.file.cursor()!!; - usz n = archive.file.read(sig_buf[..])!; + sz n = archive.file.read(sig_buf[..])!; if (n < 4) break; if (bitorder::read(sig_buf, UIntLE) != ZIP_LFH_SIG) { - archive.file.set_cursor(-3, FROM_CURSOR)!!; + archive.file.seek(-3, FROM_CURSOR)!!; continue; } ZipLFH lfh; - if (archive.file.read(((char*)&lfh.version_needed)[:ZipLFH.sizeof - 4])! != ZipLFH.sizeof - 4) break; + if (archive.file.read(((char*)&lfh.version_needed)[:ZipLFH::size - 4])! != ZipLFH::size - 4) break; lfh.signature.val = ZIP_LFH_SIG; - char[] raw_name = allocator::alloc_array(allocator, char, lfh.filename_len.val); - if (archive.file.read(raw_name)! != (usz)lfh.filename_len.val) + char[] raw_name = alloc::alloc_array(allocator, char, lfh.filename_len.val); + if (archive.file.read(raw_name)! != (sz)lfh.filename_len.val) { - allocator::free(allocator, raw_name); + alloc::free(allocator, raw_name); break; } @@ -331,10 +333,10 @@ fn ZipArchive? recover(Allocator allocator, String path) else { name = (String)codepage::decode(allocator, raw_name, CP437)!!; - allocator::free(allocator, raw_name.ptr); + alloc::free(allocator, raw_name.ptr); } - archive.file.set_cursor(lfh.extra_field_len.val, FROM_CURSOR)!!; + archive.file.seek(lfh.extra_field_len.val, FROM_CURSOR)!!; ZipEntry entry = { .name = name, @@ -351,8 +353,8 @@ fn ZipArchive? recover(Allocator allocator, String path) archive.entries.push(entry); - if (lfh.compressed_size.val > 0 && (ulong)lfh.compressed_size.val > (ulong)isz.max) return io::OVERFLOW~; - archive.file.set_cursor(lfh.compressed_size.val, FROM_CURSOR)!!; + if (lfh.compressed_size.val > 0 && (ulong)lfh.compressed_size.val > (ulong)sz::max) return io::OVERFLOW~; + archive.file.seek(lfh.compressed_size.val, FROM_CURSOR)!!; } if (archive.entries.len() == 0) return CORRUPTED_DATA~; @@ -369,22 +371,22 @@ fn void? ZipArchive.close(&self) foreach (&entry : self.entries) { - allocator::free(self.allocator, entry.name); + alloc::free(self.allocator, entry.name); } self.entries.free(); - allocator::free(self.allocator, self.mode); - allocator::free(self.allocator, self.path); - if (self.comment.len > 0) allocator::free(self.allocator, self.comment); - if (self.file) allocator::free(self.allocator, self.file); + alloc::free(self.allocator, self.mode); + alloc::free(self.allocator, self.path); + if (self.comment.len > 0) alloc::free(self.allocator, self.comment); + if (self.file) alloc::free(self.allocator, self.file); } if (self.mode.starts_with("w")) { self.file.flush()!; - ulong cd_offset = self.file.cursor()!; - ulong cd_size = 0; + long cd_offset = self.file.cursor()!; + long cd_size = 0; - for (usz i = 0; i < self.entries.len(); i++) + for (sz i = 0; i < self.entries.len(); i++) { ZipEntry* entry = self.entries.get_ref(i); ZipCDH cdh = { @@ -409,12 +411,12 @@ fn void? ZipArchive.close(&self) // Header(4) + Uncomp(8) + Comp(8) + Offset(8) ushort extra_size = 28; - extra_data = allocator::alloc_array(self.allocator, char, extra_size); + extra_data = alloc::alloc_array(self.allocator, char, extra_size); bitorder::write(ZIP64_EXTRA_ID, extra_data[:2], UShortLE); bitorder::write((ushort)(extra_size - 4), extra_data[2:2], UShortLE); - bitorder::write(entry.uncompressed_size, extra_data[4:8], ULongLE); - bitorder::write(entry.compressed_size, extra_data[12:8], ULongLE); - bitorder::write(entry.offset, extra_data[20:8], ULongLE); + bitorder::write(entry.uncompressed_size, extra_data[4:8], LongLE); + bitorder::write(entry.compressed_size, extra_data[12:8], LongLE); + bitorder::write(entry.offset, extra_data[20:8], LongLE); cdh.extra_field_len.val = extra_size; } @@ -434,11 +436,11 @@ fn void? ZipArchive.close(&self) if (is_zip64) { self.file.write(extra_data)!; - allocator::free(self.allocator, extra_data); + alloc::free(self.allocator, extra_data); } - ulong entry_record_size = (ulong)(ZipCDH.sizeof + entry.name.len + cdh.extra_field_len.val); - if (cd_size > (ulong.max - entry_record_size)) return io::OVERFLOW~; + long entry_record_size = (long)ZipCDH::size + entry.name.len + cdh.extra_field_len.val; + if (cd_size + entry_record_size <= 0) return io::OVERFLOW~; cd_size += entry_record_size; } @@ -446,15 +448,15 @@ fn void? ZipArchive.close(&self) if (cd_zip64) { - ulong eocd64_offset = self.file.cursor()!; + long eocd64_offset = self.file.cursor()!; Zip64EOCD eocd64 = { .signature.val = ZIP64_EOCD_SIG, - .size.val = (ulong)(Zip64EOCD.sizeof - 12), + .size.val = (ulong)(Zip64EOCD::size - 12), .version_made.val = 45, .version_needed.val = 45, - .count_this_disk.val = (ulong)self.entries.len(), - .count_total.val = (ulong)self.entries.len(), + .count_this_disk.val = self.entries.len(), + .count_total.val = self.entries.len(), .size_cd.val = cd_size, .offset_cd.val = cd_offset, }; @@ -480,12 +482,12 @@ fn void? ZipArchive.close(&self) encoded_comment = res; if (encoded_comment.len > 0xFFFF) { - allocator::free(self.allocator, encoded_comment.ptr); + alloc::free(self.allocator, encoded_comment.ptr); return INVALID_ARGUMENT~; } } } - defer if (encoded_comment.ptr) allocator::free(self.allocator, encoded_comment); + defer if (encoded_comment.ptr) alloc::free(self.allocator, encoded_comment); ZipEOCD eocd = { .signature.val = ZIP_EOCD_SIG, @@ -508,10 +510,11 @@ fn void? ZipArchive.close(&self) <* Extracts the entire archive to the specified directory. @param output_dir : `The directory to extract to.` + @param password : `The password if a file is encrypted.` *> -fn void? ZipArchive.extract(&self, String output_dir) => @pool() +fn void? ZipArchive.extract(&self, String output_dir, String password = "") => @pool() { - for (usz i = 0; i < self.count(); i++) + for (sz i = 0; i < self.count(); i++) { ZipEntry entry = self.stat_at(i) ?? ENTRY_NOT_FOUND~!; String out_path_str; @@ -545,7 +548,7 @@ fn void? ZipArchive.extract(&self, String output_dir) => @pool() } } - ZipEntryReader reader = self.open_reader(entry.name)!; + ZipEntryReader reader = self.open_reader(entry.name, password)!; defer (void)reader.close(); File f = file::open(out_path_str, "wb")!; @@ -553,13 +556,13 @@ fn void? ZipArchive.extract(&self, String output_dir) => @pool() char[65536] buf; while (true) { - usz? res = reader.read(&buf); + sz? res = reader.read(&buf); if (catch excuse = res) { if (excuse == io::EOF) break; return excuse~; } - usz n = res; + sz n = res; if (n == 0) break; f.write(buf[:n])!; } @@ -570,7 +573,7 @@ fn void? ZipArchive.extract(&self, String output_dir) => @pool() } // Set directory timestamps (reverse order for subdirectories) - for (usz i = self.count(); i > 0; i--) + for (sz i = self.count(); i > 0; i--) { ZipEntry entry; if (try res = self.stat_at(i - 1)) @@ -594,13 +597,13 @@ fn void? ZipArchive.extract(&self, String output_dir) => @pool() } } -fn usz ZipArchive.count(&self) => self.entries.len(); +fn sz ZipArchive.count(&self) => self.entries.len(); <* Returns metadata for the entry at the given index. @require index < self.count() *> -fn ZipEntry? ZipArchive.stat_at(&self, usz index) +fn ZipEntry? ZipArchive.stat_at(&self, sz index) { if (index >= self.entries.len()) return ENTRY_NOT_FOUND~; return self.entries.get(index); @@ -611,10 +614,9 @@ fn ZipEntry? ZipArchive.stat_at(&self, usz index) *> fn ZipEntry? ZipArchive.stat(&self, String filename) { - for (usz i = 0; i < self.entries.len(); i++) + foreach (&entry : self.entries) { - ZipEntry entry = self.entries.get(i); - if (entry.name == filename) return entry; + if (entry.name == filename) return *entry; } return ENTRY_NOT_FOUND~; } @@ -624,37 +626,21 @@ fn ZipEntry? ZipArchive.stat(&self, String filename) Reads an entire file from the archive. @param allocator : `The allocator to use.` @param filename : `The name of the file to read.` + @param password : `The password if file is encrypted.` @return `The uncompressed file data.` *> -fn char[]? ZipArchive.read_file_all(&self, Allocator allocator, String filename, ) +fn char[]? ZipArchive.read_file_all(&self, Allocator allocator, String filename, String password = "") { - ZipEntryReader reader = self.open_reader(filename)!; + ZipEntryReader reader = self.open_reader(filename, password)!; defer (void)reader.close(); ZipEntry entry = self.stat(filename)!; char[] data; - defer catch if (data.ptr) allocator::free(allocator, data); + defer catch if (data.ptr) alloc::free(allocator, data); - if (reader.method == STORE) - { - if (reader.size > (ulong)usz.max) return io::OVERFLOW~; - data = allocator::alloc_array(allocator, char, (usz)reader.size); - reader.read(data)!; - } - else if (reader.method == DEFLATE) - { - if (reader.adapter.start_offset > (ulong)isz.max) return io::OVERFLOW~; - self.file.set_cursor(reader.adapter.start_offset)!; - if (entry.compressed_size > (ulong)usz.max) return io::OVERFLOW~; - char[] compressed = allocator::alloc_array(allocator, char, (usz)entry.compressed_size); - defer allocator::free(allocator, compressed); - self.file.read(compressed)!; - data = deflate::decompress(allocator, compressed)!; - } - else - { - return UNSUPPORTED_METHOD~; - } + data = io::read_fully(allocator, &reader)!; + + if (reader.adapter.aex == 2) return data; Crc32 crc; crc.init(); @@ -700,8 +686,8 @@ fn void? ZipArchive.write_file(&self, String filename, char[] data, ZipMethod me struct ZipEntryReader (InStream) { - ulong size; // Uncompressed size - ulong pos; // Uncompressed position + long size; // Uncompressed size + long pos; // Uncompressed position ZipMethod method; // For DEFLATE Inflater* inflater; @@ -709,11 +695,11 @@ struct ZipEntryReader (InStream) char* bit_buf; } -fn usz? ZipEntryReader.read(&self, char[] buffer) @dynamic +fn sz? ZipEntryReader.read(&self, char[] buffer) @dynamic { if (self.method == STORE) { - usz n = self.adapter.read(buffer)!; + sz n = self.adapter.read(buffer)!; if (n == 0) return io::EOF~; self.pos += n; return n; @@ -722,12 +708,12 @@ fn usz? ZipEntryReader.read(&self, char[] buffer) @dynamic { if (self.inflater == null) { - self.inflater = allocator::new(self.adapter.archive.allocator, Inflater); - self.bit_buf = allocator::alloc_array(self.adapter.archive.allocator, char, 8192); + self.inflater = alloc::new(self.adapter.archive.allocator, Inflater); + self.bit_buf = alloc::alloc_array(self.adapter.archive.allocator, char, 8192); self.inflater.init(&self.adapter, self.bit_buf[:8192]); } - usz n = self.inflater.read(buffer)!; + sz n = self.inflater.read(buffer)!; if (n == 0) return io::EOF~; self.pos += n; return n; @@ -740,8 +726,8 @@ fn void? ZipEntryReader.close(&self) @dynamic { if (self.method == DEFLATE && self.inflater != null) { - allocator::free(self.adapter.archive.allocator, self.bit_buf); - allocator::free(self.adapter.archive.allocator, self.inflater); + alloc::free(self.adapter.archive.allocator, self.bit_buf); + alloc::free(self.adapter.archive.allocator, self.inflater); self.inflater = null; self.bit_buf = null; } @@ -751,40 +737,31 @@ fn void? ZipEntryReader.close(&self) @dynamic fn char? ZipEntryReader.read_byte(&self) @dynamic { char[1] b; - usz n = self.read(&b)!; + sz n = self.read(&b)!; if (n == 0) return io::EOF~; return b[0]; } -fn usz ZipEntryReader.len(&self) @dynamic +fn long ZipEntryReader.len(&self) @dynamic { - if (self.size > (ulong)usz.max) return usz.max; - return (usz)self.size; + return self.size; } -fn ulong? ZipEntryReader.available(&self) @dynamic +fn long? ZipEntryReader.available(&self) @dynamic { return self.size - self.pos; } -fn usz? ZipEntryReader.seek(&self, isz offset, Seek seek) @dynamic -{ - self.set_cursor((long)offset, (SeekOrigin)seek.ordinal)!; - long size = self.cursor()!; - if (size > (ulong)usz.max) return io::OVERFLOW~; - return (usz)size; -} - fn long? ZipEntryReader.cursor(&self) @dynamic { return self.pos; } -fn void? ZipEntryReader.set_cursor(&self, long offset, SeekOrigin seek) @dynamic +fn void? ZipEntryReader.seek(&self, long offset, SeekOrigin seek = FROM_START) @dynamic { if (self.method == DEFLATE) return io::UNSUPPORTED_OPERATION~; - ulong new_pos = self.pos; + long new_pos = self.pos; switch (seek) { case FROM_START: @@ -803,18 +780,18 @@ fn void? ZipEntryReader.set_cursor(&self, long offset, SeekOrigin seek) @dynamic <* Opens a reader for an entry. @param filename : `The name of the file to read.` + @param password : `The password if file is encrypted.` @return `A reader for the entry's data.` *> -fn ZipEntryReader? ZipArchive.open_reader(&self, String filename) +fn ZipEntryReader? ZipArchive.open_reader(&self, String filename, String password = "") { ZipEntry? entry = self.stat(filename); if (catch entry) return ENTRY_NOT_FOUND~; - if (entry.is_encrypted) return ENCRYPTED_FILE~; self.file.flush()!; - if (entry.offset > (ulong)isz.max) return io::OVERFLOW~; - self.file.set_cursor(entry.offset)!; + if (entry.offset > (ulong)sz::max) return io::OVERFLOW~; + self.file.seek(entry.offset)!; ZipLFH lfh; io::read_any(self.file, &lfh)!; if (lfh.signature.val != ZIP_LFH_SIG) return CORRUPTED_DATA~; @@ -822,8 +799,8 @@ fn ZipEntryReader? ZipArchive.open_reader(&self, String filename) ZipEntryReader reader; reader.adapter.archive = self; - ulong start_offset = entry.offset + ZipLFH.sizeof + lfh.filename_len.val + lfh.extra_field_len.val; - if (start_offset > (ulong)isz.max) return io::OVERFLOW~; + long start_offset = entry.offset + ZipLFH::size + lfh.filename_len.val + lfh.extra_field_len.val; + if (start_offset > sz::max) return io::OVERFLOW~; reader.adapter.start_offset = start_offset; // For STORE: adapter.size is uncompressed size. // For DEFLATE: adapter.size is compressed size. @@ -833,6 +810,88 @@ fn ZipEntryReader? ZipArchive.open_reader(&self, String filename) reader.size = entry.uncompressed_size; reader.method = entry.method; + // AE-X decryption. + if (entry.is_encrypted) + { + if (entry.method != AEX) return ENCRYPTED_FILE~; + if (!password.len) return PASSWORD_NEEDED~; + + char aex_strength; + ushort aex_method; + ushort aex_version; + + self.file.seek(lfh.filename_len.val, FROM_CURSOR)!; + long end = self.file.cursor()! + lfh.extra_field_len.val; + while (self.file.cursor()! < end) + { + ushort id = io::read_le_ushort(self.file)!; + ushort size = io::read_le_ushort(self.file)!; + + if (id == AEX_EXTRA_ID) + { + aex_version = io::read_le_ushort(self.file)!; + io::read_le_ushort(self.file)!; + aex_strength = self.file.read_byte()!; + aex_method = io::read_le_ushort(self.file)!; + continue; + } + self.file.seek(size, FROM_CURSOR)!; + } + if (!aex_version) return CORRUPTED_DATA~; + + AesType aes_type; + sz key_len; + switch (aex_strength) + { + case 1: key_len = 16; aes_type = AES128; + case 2: key_len = 24; aes_type = AES192; + case 3: key_len = 32; aes_type = AES256; + default: return CORRUPTED_DATA~; + } + sz salt_len = key_len / 2; + + @pool() + { + char[] buf = mem::temp_array(char, salt_len + 2); + self.file.read(buf)!; + + if (buf.len < salt_len + 2) return CORRUPTED_DATA~; + + char[] salt = buf[0:salt_len]; + char[] pv_stored = buf[salt_len:2]; + + sz cipher_len = (sz)entry.compressed_size - salt_len - 2 - 10; + if (cipher_len < 0) return CORRUPTED_DATA~; + + char[] cipher = mem::temp_array(char, cipher_len); + self.file.read(cipher)!; + + char[] hmac_stored = mem::temp_array(char, 10); + self.file.read(hmac_stored)!; + + sz dk_len = key_len * 2 + 2; + char[] dk = mem::temp_array(char, dk_len); + sha1::pbkdf2(password, salt, 1000, dk); + + char[] aes_key = dk[0:key_len ]; + char[] mac_key = dk[key_len:key_len]; + char[] pv_derived = dk[key_len*2:2]; + + if (pv_stored != pv_derived) return PASSWORD_MISMATCH~; + + char[*] hmac_calc = sha1::hmac(mac_key, cipher); + if (hmac_stored[:10] != hmac_calc[:10]) return CORRUPTED_DATA~; + + char[16] iv = { [0] = 1 }; + reader.adapter.aes.init(aes_type, aes_key, iv[..], CTR_LE); + }; + + reader.adapter.aex = aex_version; + reader.adapter.start_offset += (long)salt_len + 2; + reader.adapter.size = entry.compressed_size - salt_len - 2 - 10; + reader.method = (ZipMethod)aex_method; + } + return reader; } @@ -842,21 +901,21 @@ struct ZipEntryWriter (OutStream) ZipEntry entry; Crc32 crc; ZipLFH lfh; - ulong lfh_offset; + long lfh_offset; Deflater deflater; bool use_deflater; } -fn usz? ZipEntryWriter.write(&self, char[] bytes) @dynamic +fn sz? ZipEntryWriter.write(&self, char[] bytes) @dynamic { if (bytes.len == 0) return 0; self.crc.update(bytes); - self.entry.uncompressed_size += (ulong)bytes.len; + self.entry.uncompressed_size += bytes.len; if (self.entry.method == STORE) { - usz n = self.archive.file.write(bytes)!; + sz n = self.archive.file.write(bytes)!; self.entry.compressed_size += n; return n; } @@ -885,16 +944,16 @@ fn void? ZipEntryWriter.close(&self) @dynamic self.lfh.crc32.val = self.entry.crc32; // ZIP64 sentinel: readers typically fallback to Central Directory for actual sizes. - self.lfh.compressed_size.val = (uint)math::min(self.entry.compressed_size, (ulong)0xFFFFFFFF); - self.lfh.uncompressed_size.val = (uint)math::min(self.entry.uncompressed_size, (ulong)0xFFFFFFFF); + self.lfh.compressed_size.val = (uint)math::min(self.entry.compressed_size, (long)0xFFFFFFFF); + self.lfh.uncompressed_size.val = (uint)math::min(self.entry.uncompressed_size, (long)0xFFFFFFFF); long end_pos = self.archive.file.cursor()!; - if (self.lfh_offset > (long)isz.max) return io::OVERFLOW~; - self.archive.file.set_cursor(self.lfh_offset)!; + if (self.lfh_offset > (long)sz::max) return io::OVERFLOW~; + self.archive.file.seek(self.lfh_offset)!; io::write_any(self.archive.file, &self.lfh)!; - self.archive.file.set_cursor(end_pos)!; + self.archive.file.seek(end_pos)!; self.archive.entries.push(self.entry); } @@ -912,7 +971,7 @@ fn ZipEntryWriter? ZipArchive.open_writer(&self, String filename, ZipMethod meth ZipEntryWriter writer; writer.archive = self; writer.entry.name = filename.copy(self.allocator); - defer catch allocator::free(self.allocator, writer.entry.name); + defer catch alloc::free(self.allocator, writer.entry.name); writer.entry.method = method; writer.entry.offset = self.file.cursor()!; @@ -952,24 +1011,29 @@ fn ZipEntryWriter? ZipArchive.open_writer(&self, String filename, ZipMethod meth struct ArchiveStreamAdapter (InStream) @private { ZipArchive* archive; - ulong start_offset; - ulong size; - ulong pos; + long start_offset; + long size; + long pos; + + // For AEX decryption + ushort aex; // 0: unencrypted, 1: AE-1, 2: AE-2 + Aes aes; } -fn usz? ArchiveStreamAdapter.read(&self, char[] buffer) @dynamic +fn sz? ArchiveStreamAdapter.read(&self, char[] buffer) @dynamic { if (self.pos >= self.size) return 0; - usz to_read = (usz)math::min((ulong)buffer.len, self.size - self.pos); + sz to_read = (sz)math::min((long)buffer.len, self.size - self.pos); if (to_read == 0) return 0; - ulong abs_pos = self.start_offset + self.pos; - if (abs_pos > (ulong)isz.max) return io::OVERFLOW~; + long abs_pos = self.start_offset + self.pos; + if (abs_pos > sz::max) return io::OVERFLOW~; // Note: ZipArchive shared file handle access is not thread-safe. - self.archive.file.set_cursor(abs_pos)!; - usz n = self.archive.file.read(buffer[:to_read])!; + self.archive.file.seek(abs_pos)!; + sz n = self.archive.file.read(buffer[:to_read])!; + if (self.aex) self.aes.decrypt_buffer(buffer[:n], buffer[:n]); self.pos += n; return n; } @@ -1040,19 +1104,20 @@ const uint ZIP64_EOCD_SIG @private = 0x06064B50; const uint ZIP64_LOCATOR_SIG @private = 0x07064B50; const ushort ZIP64_EXTRA_ID @private = 0x0001; +const ushort AEX_EXTRA_ID @private = 0x9901; struct Zip64EOCD @packed @private { UIntLE signature; - ULongLE size; // Size of remaining record + LongLE size; // Size of remaining record UShortLE version_made; UShortLE version_needed; UIntLE disk_num; UIntLE disk_start; - ULongLE count_this_disk; - ULongLE count_total; - ULongLE size_cd; - ULongLE offset_cd; + LongLE count_this_disk; + LongLE count_total; + LongLE size_cd; + LongLE offset_cd; // char[] custom_data; } @@ -1060,7 +1125,7 @@ struct Zip64Locator @packed @private { UIntLE signature; UIntLE disk_start; - ULongLE offset_eocd; + LongLE offset_eocd; UIntLE total_disks; } @@ -1068,9 +1133,9 @@ struct Zip64ExtraField @private { ushort header_id; ushort size; - ulong uncompressed_size; - ulong compressed_size; - ulong offset; + long uncompressed_size; + long compressed_size; + long offset; uint disk_start; } @@ -1120,7 +1185,7 @@ fn ushort time_to_dos_date(Time t) @private fn Time dos_date_time_to_time(ushort dos_date, ushort dos_time) @private { - int sec = (int)((dos_time & 0x1F) * 2); + int sec = (int)((dos_time & 0x1F) * 2u); int min = (int)((dos_time >> 5) & 0x3F); int hour = (int)((dos_time >> 11) & 0x1F); @@ -1162,7 +1227,7 @@ fn Time dos_date_time_to_time(ushort dos_date, ushort dos_time) @private fn bool is_valid_utf8(char[] bytes) @private { - usz i = 0; + sz i = 0; while (i < bytes.len) { char lead = bytes[i]; diff --git a/test/stdlib/src/core/alloc.c3 b/test/stdlib/src/core/alloc.c3 new file mode 100644 index 0000000..19f1fb3 --- /dev/null +++ b/test/stdlib/src/core/alloc.c3 @@ -0,0 +1,417 @@ +module std::core::mem::alloc; +import std::math; + +struct TrackingEnv +{ + String file; + String function; + int line; +} + +enum AllocInitType +{ + NO_ZERO, + ZERO +} + +interface Allocator +{ + <* + Acquire memory from the allocator, with the given alignment and initialization type. + + @require !alignment || math::is_power_of_2(alignment) + @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` + @require size > 0 : "The size must be 1 or more" + @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY + *> + fn void*? acquire(sz size, AllocInitType init_type, sz alignment = 0); + + <* + Resize acquired memory from the allocator, with the given new size and alignment. + + @require !alignment || math::is_power_of_2(alignment) + @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` + @require ptr != null + @require new_size > 0 + @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY + *> + fn void*? resize(void* ptr, sz new_size, sz alignment = 0); + + <* + Release memory acquired using `acquire` or `resize`. + + @require ptr != null : "Empty pointers should never be released" + *> + fn void release(void* ptr, bool aligned); +} + +alias MemoryAllocFn = fn char[]?(sz); + + +<* + @require size >= 0 : "Invalid memory size" +*> +macro void* malloc(Allocator allocator, sz size) @nodiscard +{ + return malloc_try(allocator, size)!!; +} + +<* + @require size >= 0 : "Invalid memory size" +*> +macro void*? malloc_try(Allocator allocator, sz size) @nodiscard +{ + if (!size) return null; + $if env::TESTING: + char* data = allocator.acquire(size, NO_ZERO)!; + mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); + return data; + $else + return allocator.acquire(size, NO_ZERO); + $endif +} + +<* + @require size >= 0 : "Invalid memory size" +*> +macro void* calloc(Allocator allocator, sz size) @nodiscard +{ + return calloc_try(allocator, size)!!; +} + +<* + @require size >= 0 : "Invalid memory size" +*> +macro void*? calloc_try(Allocator allocator, sz size) @nodiscard +{ + if (!size) return null; + return allocator.acquire(size, ZERO); +} + +<* + @require new_size >= 0 : "Invalid memory size" +*> +macro void* realloc(Allocator allocator, void* ptr, sz new_size) @nodiscard +{ + return realloc_try(allocator, ptr, new_size)!!; +} + +<* + @require new_size >= 0 : "Invalid memory size" +*> +macro void*? realloc_try(Allocator allocator, void* ptr, sz new_size) @nodiscard +{ + if (!new_size) + { + free(allocator, ptr); + return null; + } + if (!ptr) return allocator.acquire(new_size, NO_ZERO); + return allocator.resize(ptr, new_size); +} + +macro void free(Allocator allocator, void* ptr) +{ + if (!ptr) return; + $if env::TESTING: + ((char*)ptr)[0] = 0xBA; + $endif + allocator.release(ptr, false); +} + +<* + @require size >= 0 : "Invalid memory size" + @require alignment > 0 : "Invalid alignment" +*> +macro void*? malloc_aligned(Allocator allocator, sz size, sz alignment) @nodiscard +{ + if (!size) return null; + $if env::TESTING: + char* data = allocator.acquire(size, NO_ZERO, alignment)!; + mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); + return data; + $else + return allocator.acquire(size, NO_ZERO, alignment); + $endif +} + +<* + @require size >= 0 : "Invalid memory size" + @require alignment > 0 : "Invalid alignment" +*> +macro void*? calloc_aligned(Allocator allocator, sz size, sz alignment) @nodiscard +{ + if (!size) return null; + return allocator.acquire(size, ZERO, alignment); +} + +<* + @require new_size >= 0 : "Invalid memory size" + @require alignment > 0 : "Invalid alignment" +*> +macro void*? realloc_aligned(Allocator allocator, void* ptr, sz new_size, sz alignment) @nodiscard +{ + if (!new_size) + { + free_aligned(allocator, ptr); + return null; + } + if (!ptr) + { + return malloc_aligned(allocator, new_size, alignment); + } + return allocator.resize(ptr, new_size, alignment); +} + +macro void free_aligned(Allocator allocator, void* ptr) +{ + if (!ptr) return; + $if env::TESTING: + ((char*)ptr)[0] = 0xBA; + $endif + allocator.release(ptr, aligned: true); +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" +*> +macro new(Allocator allocator, $Type, #init = ...) @nodiscard @safemacro +{ + $if $defined(#init): + $Type* val = malloc(allocator, $Type::size); + *val = #init; + return val; + $else + return ($Type*)calloc(allocator, $Type::size); + $endif +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" +*> +macro new_try(Allocator allocator, $Type, #init = ...) @nodiscard @safemacro +{ + $if $defined(#init): + $Type* val = malloc_try(allocator, $Type::size)!; + *val = #init; + return val; + $else + return ($Type*)calloc_try(allocator, $Type::size); + $endif +} + +<* + Allocate using an aligned allocation. This is necessary for types with a default memory alignment + exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" +*> +macro new_aligned(Allocator allocator, $Type, #init = ...) @nodiscard @safemacro +{ + $if $defined(#init): + $Type* val = malloc_aligned(allocator, $Type::size, $Type::alignment)!; + *val = #init; + return val; + $else + return ($Type*)calloc_aligned(allocator, $Type::size, $Type::alignment); + $endif +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT + @require padding >= 0 : "Invalid padding size" +*> +macro new_with_padding(Allocator allocator, $Type, sz padding) @nodiscard +{ + return ($Type*)calloc_try(allocator, $Type::size + padding); +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" +*> +macro alloc(Allocator allocator, $Type) @nodiscard +{ + return ($Type*)malloc(allocator, $Type::size); +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" +*> +macro alloc_try(Allocator allocator, $Type) @nodiscard +{ + return ($Type*)malloc_try(allocator, $Type::size); +} + +<* + Allocate using an aligned allocation. This is necessary for types with a default memory alignment + exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. +*> +macro alloc_aligned(Allocator allocator, $Type) @nodiscard +{ + return ($Type*)malloc_aligned(allocator, $Type::size, $Type::alignment); +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT + @require padding >= 0 : "Invalid padding size" +*> +macro alloc_with_padding(Allocator allocator, $Type, sz padding) @nodiscard +{ + return ($Type*)malloc_try(allocator, $Type::size + padding); +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro new_array(Allocator allocator, $Type, sz elements) @nodiscard +{ + return new_array_try(allocator, $Type, elements)!!; +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro new_array_try(Allocator allocator, $Type, sz elements) @nodiscard +{ + return (($Type*)calloc_try(allocator, $Type::size * elements))[:elements]; +} + +<* + Allocate using an aligned allocation. This is necessary for types with a default memory alignment + exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro new_array_aligned(Allocator allocator, $Type, sz elements) @nodiscard +{ + return (($Type*)calloc_aligned(allocator, $Type::size * elements, $Type::alignment))[:elements]!!; +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro alloc_array(Allocator allocator, $Type, sz elements) @nodiscard +{ + return alloc_array_try(allocator, $Type, elements)!!; +} + +<* + Allocate using an aligned allocation. This is necessary for types with a default memory alignment + exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro alloc_array_aligned(Allocator allocator, $Type, sz elements) @nodiscard +{ + return (($Type*)malloc_aligned(allocator, $Type::size * elements, $Type::alignment))[:elements]!!; +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro alloc_array_try(Allocator allocator, $Type, sz elements) @nodiscard +{ + return (($Type*)malloc_try(allocator, $Type::size * elements))[:elements]; +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro realloc_array(Allocator allocator, void* ptr, $Type, sz elements) @nodiscard +{ + return realloc_array_try(allocator, ptr, $Type, elements)!!; +} + +<* + Allocate using an aligned allocation. This is necessary for types with a default memory alignment + exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro realloc_array_aligned(Allocator allocator, void* ptr, $Type, sz elements) @nodiscard +{ + return (($Type*)realloc_aligned(allocator, ptr, $Type::size * elements, $Type::alignment))[:elements]!!; +} + +<* + @require $Type::alignment <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require elements >= 0 : "Elements may not be negative" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro realloc_array_try(Allocator allocator, void* ptr, $Type, sz elements) @nodiscard +{ + return (($Type*)realloc_try(allocator, ptr, $Type::size * elements))[:elements]; +} + +<* + Clone a value. + + @param [&inout] allocator : "The allocator to use to clone" + @param value : "The value to clone" + @return "A pointer to the cloned value" + @require @alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead" +*> +macro clone(Allocator allocator, value) @nodiscard +{ + return new(allocator, $typeof(value), value); +} + +<* + @param [&inout] allocator : "The allocator used to clone" + @param slice : "The slice to clone" + @return "A pointer to the cloned slice" + + @require @kindof(slice) == SLICE || @kindof(slice) == ARRAY +*> +macro clone_slice(Allocator allocator, slice) @nodiscard +{ + if (!lengthof(slice)) return {}; + + var $Type = $typeof(slice[0]); + + $Type[] new_arr = new_array(allocator, $Type, slice.len); + mem::copy(new_arr.ptr, &slice[0], slice.len * $Type::size); + + return new_arr; +} + +<* + Clone overaligned values. Must be released using free_aligned. + + @param [&inout] allocator : "The allocator to use to clone" + @param value : "The value to clone" + @return "A pointer to the cloned value" +*> +macro clone_aligned(Allocator allocator, value) @nodiscard +{ + return new_aligned(allocator, $typeof(value), value)!!; +} + +fn any clone_any(Allocator allocator, any value) @nodiscard +{ + sz size = value.type.size; + void* data = malloc(allocator, size); + mem::copy(data, value.ptr, size); + return any_make(data, value.type); +} + + + + + + diff --git a/test/stdlib/src/core/allocators.c3 b/test/stdlib/src/core/allocators.c3 new file mode 100644 index 0000000..b68be3c --- /dev/null +++ b/test/stdlib/src/core/allocators.c3 @@ -0,0 +1,191 @@ +module std::core::mem::allocators; + +// C3 has several different allocators available: +// +// Name Arena Uses buffer OOM Fallback? Mark? Reset? +// ArenaAllocator Yes Yes No Yes Yes +// BackedArenaAllocator Yes No Yes Yes Yes +// DynamicArenaAllocator Yes No Yes No Yes +// HeapAllocator No No No No No *Note: Not for normal use +// LibcAllocator No No No No No *Note: Wraps malloc +// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem +// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool +// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator +// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes + +// Heap allocator global data + +alias mem @builtin = thread_allocator ; + +// Temp allocator global data + +alias tmem @builtin = current_temp; +typedef PoolState = TempAllocator*; +tlocal Allocator current_temp = &LAZY_TEMP; +tlocal TempAllocator* top_temp; +tlocal bool auto_create_temp = false; + +<* + Push the current pool manually, must be partnered with a `pop_pool(state)` + + @param reserve : "A hint on how much memory should be reserved on the old temp allocator for out of order allocations" + @require reserve >= 0 : "Reserve may not be negative" + @returns "The stored state, to pass into the 'pop_pool' call" +*> +fn PoolState push_pool(sz reserve = 0) +{ + Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand(); + current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!; + return (PoolState)old.ptr; +} + +<* + Pop the current pool manually after a `push_pool` + + @param old : "The old state to restore to, from the push_pool call" +*> +fn void pop_pool(PoolState old) +{ + TempAllocator* temp = (TempAllocator*)old; + current_temp = temp; + temp.reset(); +} + +<* + @require !top_temp : "This should never be called when temp already exists" +*> +fn Allocator create_temp_allocator(Allocator allocator, sz size, sz reserve, sz min_size, sz realloc_size) @private +{ + return current_temp = top_temp = allocators::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!; +} + +<* + Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory. +*> +fn void destroy_temp_allocators() +{ + if (!top_temp) return; + top_temp.free(); + top_temp = null; + current_temp = &LAZY_TEMP; +} + +// Helper functionality for building allocators: + +<* Default prefix for holding a size in a malloc block *> +const DEFAULT_SIZE_PREFIX = sz::size; +<* Default alignment required *> +const DEFAULT_SIZE_PREFIX_ALIGNMENT = sz::alignment; + +fn sz alignment_for_allocation(sz alignment) @inline +{ + return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment; +} + +struct AlignedBlock +{ + sz len; + void* start; +} + +macro void? @aligned_free(#free_fn, void* old_pointer) +{ + AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; + $if @kindof(#free_fn(desc.start)) == OPTIONAL: + #free_fn(desc.start)!; + $else + #free_fn(desc.start); + $endif +} + +macro void? @aligned_free_fn(context, #free_fn, void* old_pointer) +{ + AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; + $if @kindof(#free_fn(context, desc.start)) == OPTIONAL: + #free_fn(context, desc.start)!; + $else + #free_fn(context, desc.start); + $endif +} + +<* + @require bytes > 0 + @require alignment > 0 +*> +macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, sz bytes, sz alignment) +{ + AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; + void* data_start = desc.start; + void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!; + mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); + $if @kindof(#free_fn(data_start)) == OPTIONAL: + #free_fn(data_start)!; + $else + #free_fn(data_start); + $endif + return new_data; +} + +<* + @require bytes > 0 + @require alignment > 0 + @require bytes <= sz::max +*> +macro void*? @aligned_alloc(#alloc_fn, sz bytes, sz alignment) +{ + if (alignment < void*::alignment) alignment = void*::alignment; + sz header = AlignedBlock::size + alignment; + sz alignsize = bytes + header; + $if @kindof(#alloc_fn(bytes)) == OPTIONAL: + void* data = #alloc_fn(alignsize)!; + $else + void* data = #alloc_fn(alignsize); + $endif + void* mem = mem::aligned_pointer(data + AlignedBlock::size, alignment); + AlignedBlock* desc = (AlignedBlock*)mem - 1; + assert(mem > data); + *desc = { bytes, data }; + return mem; +} + +<* + @require bytes > 0 + @require alignment > 0 + @require bytes <= sz::max +*> +macro void*? @aligned_alloc_fn(context, #alloc_fn, sz bytes, sz alignment) +{ + if (alignment < void*::alignment) alignment = void*::alignment; + sz header = AlignedBlock::size + alignment; + sz alignsize = bytes + header; + $if @kindof(#alloc_fn(context, bytes)) == OPTIONAL: + void* data = #alloc_fn(context, alignsize)!; + $else + void* data = #alloc_fn(context, alignsize); + $endif + void* mem = mem::aligned_pointer(data + AlignedBlock::size, alignment); + AlignedBlock* desc = (AlignedBlock*)mem - 1; + assert(mem > data); + *desc = { bytes, data }; + return mem; +} + + + +<* + @require bytes > 0 + @require alignment > 0 +*> +macro void*? @aligned_realloc_fn(context, #calloc_fn, #free_fn, void* old_pointer, sz bytes, sz alignment) +{ + AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; + void* data_start = desc.start; + void* new_data = @aligned_alloc_fn(context, #calloc_fn, bytes, alignment)!; + mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); + $if @kindof(#free_fn(context, data_start)) == OPTIONAL: + #free_fn(context, data_start)!; + $else + #free_fn(context, data_start); + $endif + return new_data; +} diff --git a/test/stdlib/src/core/allocators/arena_allocator.c3 b/test/stdlib/src/core/allocators/arena_allocator.c3 index d67ae90..3cc259a 100644 --- a/test/stdlib/src/core/allocators/arena_allocator.c3 +++ b/test/stdlib/src/core/allocators/arena_allocator.c3 @@ -1,7 +1,7 @@ // Copyright (c) 2023-2025 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. -module std::core::mem::allocator; +module std::core::mem::allocators; import std::math; // The arena allocator allocates up to its maximum data @@ -11,7 +11,7 @@ import std::math; struct ArenaAllocator (Allocator) { char[] data; - usz used; + sz used; } <* @@ -51,7 +51,7 @@ macro ArenaAllocator* wrap(char[] bytes) @return `The value to pass to 'reset' in order to reset to the current use.` *> -fn usz ArenaAllocator.mark(&self) => self.used; +fn sz ArenaAllocator.mark(&self) => self.used; <* Reset to a previous mark. @@ -59,7 +59,7 @@ fn usz ArenaAllocator.mark(&self) => self.used; @param mark : `The previous mark.` @require mark <= self.used : "Invalid mark - out of range" *> -fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark; +fn void ArenaAllocator.reset(&self, sz mark) => self.used = mark; <* Implements the Allocator interface method. @@ -69,11 +69,11 @@ fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark; fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic { assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator."); - ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof; + ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader::size; // Reclaim memory if it's the last element. if (ptr + header.size == &self.data[self.used]) { - self.used -= header.size + ArenaAllocatorHeader.sizeof; + self.used -= header.size + ArenaAllocatorHeader::size; } } @@ -86,18 +86,18 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic @require size > 0 @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? ArenaAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); - usz total_len = self.data.len; + sz total_len = self.data.len; if (size > total_len) return mem::INVALID_ALLOC_SIZE~; void* start_mem = self.data.ptr; - void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof; + void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader::size; void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); - usz end = (usz)(mem - self.data.ptr) + size; + sz end = (sz)(mem - self.data.ptr) + size; if (end > total_len) return mem::OUT_OF_MEMORY~; self.used = end; - ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof; + ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader::size; header.size = size; if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); return mem; @@ -112,14 +112,14 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a @require size > 0 @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic +fn void*? ArenaAllocator.resize(&self, void *old_pointer, sz size, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator."); - usz total_len = self.data.len; + sz total_len = self.data.len; if (size > total_len) return mem::INVALID_ALLOC_SIZE~; - ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof; - usz old_size = header.size; + ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader::size; + sz old_size = header.size; // Do last allocation and alignment match? if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment)) { @@ -129,7 +129,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen } else { - usz new_used = self.used + size - old_size; + sz new_used = self.used + size - old_size; if (new_used > total_len) return mem::OUT_OF_MEMORY~; self.used = new_used; } @@ -146,6 +146,6 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen struct ArenaAllocatorHeader @local { - usz size; + sz size; char[*] data; } diff --git a/test/stdlib/src/core/allocators/backed_arena_allocator.c3 b/test/stdlib/src/core/allocators/backed_arena_allocator.c3 index 57f7100..e02c02b 100644 --- a/test/stdlib/src/core/allocators/backed_arena_allocator.c3 +++ b/test/stdlib/src/core/allocators/backed_arena_allocator.c3 @@ -1,4 +1,4 @@ -module std::core::mem::allocator; +module std::core::mem::allocators; import std::io, std::math; <* @@ -14,38 +14,38 @@ struct BackedArenaAllocator (Allocator) { Allocator backing_allocator; ExtraPage* last_page; - usz used; - usz capacity; + sz used; + sz capacity; char[*] data; } struct AllocChunk @local { - usz size; + sz size; char[*] data; } -const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u; +const sz PAGE_IS_ALIGNED @local = sz::min; struct ExtraPage @local { ExtraPage* prev_page; void* start; - usz mark; - usz size; - usz ident; + sz mark; + sz size; + sz ident; char[*] data; } -macro usz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED; +macro sz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED; macro bool ExtraPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED; <* @require size >= 16 *> -fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator) +fn BackedArenaAllocator*? new_backed_allocator(sz size, Allocator allocator) { - BackedArenaAllocator* temp = allocator::alloc_with_padding(allocator, BackedArenaAllocator, size)!; + BackedArenaAllocator* temp = alloc::alloc_with_padding(allocator, BackedArenaAllocator, size)!; temp.last_page = null; temp.backing_allocator = allocator; temp.used = 0; @@ -57,21 +57,21 @@ fn void BackedArenaAllocator.destroy(&self) { self.reset(0); if (self.last_page) (void)_free_page(self, self.last_page); - allocator::free(self.backing_allocator, self); + alloc::free(self.backing_allocator, self); } -fn usz BackedArenaAllocator.mark(&self) => self.used; +fn sz BackedArenaAllocator.mark(&self) => self.used; fn void BackedArenaAllocator.release(&self, void* old_pointer, bool) @dynamic { - usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX); + sz old_size = *(sz*)(old_pointer - DEFAULT_SIZE_PREFIX); if (old_pointer + old_size == &self.data[self.used]) { self.used -= old_size; - asan::poison_memory_region(&self.data[self.used], old_size); + asan::poison_memory_region(&self.data[self.used], (usz)old_size); } } -fn void BackedArenaAllocator.reset(&self, usz mark) +fn void BackedArenaAllocator.reset(&self, sz mark) { ExtraPage *last_page = self.last_page; while (last_page && last_page.mark > mark) @@ -85,13 +85,13 @@ fn void BackedArenaAllocator.reset(&self, usz mark) $if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER: if (!last_page) { - usz cleaned = self.used - mark; + sz cleaned = self.used - mark; if (cleaned > 0) { $if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER: self.data[mark : cleaned] = 0xAA; $endif - asan::poison_memory_region(&self.data[mark], cleaned); + asan::poison_memory_region(&self.data[mark], (usz)cleaned); } } $endif @@ -104,7 +104,7 @@ fn void? _free_page(BackedArenaAllocator* self, ExtraPage* page) @inline @local return self.backing_allocator.release(mem, page.is_aligned()); } -fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, usz alignment) @inline @local +fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, sz size, sz alignment) @inline @local { // Then the actual start pointer: void* real_pointer = page.start; @@ -117,7 +117,7 @@ fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, u pointer_to_prev = &((*pointer_to_prev).prev_page); } *pointer_to_prev = page.prev_page; - usz page_size = page.pagesize(); + sz page_size = page.pagesize(); // Clear on size > original size. void* data = self.acquire(size, NO_ZERO, alignment)!; mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); @@ -125,14 +125,14 @@ fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, u return data; } -fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic +fn void*? BackedArenaAllocator.resize(&self, void* pointer, sz size, sz alignment) @dynamic { - AllocChunk *chunk = pointer - AllocChunk.sizeof; - if (chunk.size == (usz)-1) + AllocChunk *chunk = pointer - AllocChunk::size; + if (chunk.size == -1) { assert(self.last_page, "Realloc of unrelated pointer"); // First grab the page - ExtraPage *page = pointer - ExtraPage.sizeof; + ExtraPage *page = pointer - ExtraPage::size; return _realloc_page(self, page, size, alignment); } @@ -147,24 +147,24 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm @require !alignment || math::is_power_of_2(alignment) @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` *> -fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? BackedArenaAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); void* start_mem = &self.data; void* starting_ptr = start_mem + self.used; - void* aligned_header_start = mem::aligned_pointer(starting_ptr, AllocChunk.alignof); - void* mem = aligned_header_start + AllocChunk.sizeof; - if (alignment > AllocChunk.alignof) + void* aligned_header_start = mem::aligned_pointer(starting_ptr, AllocChunk::alignment); + void* mem = aligned_header_start + AllocChunk::size; + if (alignment > AllocChunk::alignment) { mem = mem::aligned_pointer(mem, alignment); } - usz new_usage = (usz)(mem - start_mem) + size; + sz new_usage = (sz)(mem - start_mem) + size; // Arena allocation, simple! if (new_usage <= self.capacity) { - asan::unpoison_memory_region(starting_ptr, new_usage - self.used); - AllocChunk* chunk_start = mem - AllocChunk.sizeof; + asan::unpoison_memory_region(starting_ptr, (usz)(new_usage - self.used)); + AllocChunk* chunk_start = mem - AllocChunk::size; chunk_start.size = size; self.used = new_usage; if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); @@ -178,17 +178,17 @@ fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, if (alignment > mem::DEFAULT_MEM_ALIGNMENT) { // This is actually simpler, since it will create the offset for us. - usz total_alloc_size = mem::aligned_offset(ExtraPage.sizeof + size, alignment); + sz total_alloc_size = mem::aligned_offset(ExtraPage::size + size, alignment); if (init_type == ZERO) { - mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; + mem = alloc::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } else { - mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; + mem = alloc::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } void* start = mem; - mem += mem::aligned_offset(ExtraPage.sizeof, alignment); + mem += mem::aligned_offset(ExtraPage::size, alignment); page = (ExtraPage*)mem - 1; page.start = start; page.size = size | PAGE_IS_ALIGNED; @@ -196,20 +196,20 @@ fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, else { // Here we might need to pad - usz padded_header_size = mem::aligned_offset(ExtraPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT); - usz total_alloc_size = padded_header_size + size; + sz padded_header_size = mem::aligned_offset(ExtraPage::size, mem::DEFAULT_MEM_ALIGNMENT); + sz total_alloc_size = padded_header_size + size; void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!; // Find the page. - page = alloc + padded_header_size - ExtraPage.sizeof; - assert(mem::ptr_is_aligned(page, BackedArenaAllocator.alignof)); + page = alloc + padded_header_size - ExtraPage::size; + assert(mem::ptr_is_aligned(page, BackedArenaAllocator::alignment)); assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT)); page.start = alloc; page.size = size; } // Mark it as a page - page.ident = ~(usz)0; + page.ident = ~(sz)0; // Store when it was created page.mark = ++self.used; // Hook up the page. diff --git a/test/stdlib/src/core/allocators/dynamic_arena.c3 b/test/stdlib/src/core/allocators/dynamic_arena.c3 index a25ee27..6d065f3 100644 --- a/test/stdlib/src/core/allocators/dynamic_arena.c3 +++ b/test/stdlib/src/core/allocators/dynamic_arena.c3 @@ -1,7 +1,7 @@ // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. -module std::core::mem::allocator; +module std::core::mem::allocators; import std::math; <* @@ -20,14 +20,14 @@ struct DynamicArenaAllocator (Allocator) Allocator backing_allocator; DynamicArenaPage* page; DynamicArenaPage* unused_page; - usz page_size; + sz page_size; } <* @param [&inout] allocator @require page_size >= 128 *> -fn void DynamicArenaAllocator.init(&self, Allocator allocator, usz page_size) +fn void DynamicArenaAllocator.init(&self, Allocator allocator, sz page_size) { self.page = null; self.unused_page = null; @@ -41,16 +41,16 @@ fn void DynamicArenaAllocator.free(&self) while (page) { DynamicArenaPage* next_page = page.prev_arena; - allocator::free(self.backing_allocator, page.memory); - allocator::free(self.backing_allocator, page); + alloc::free(self.backing_allocator, page.memory); + alloc::free(self.backing_allocator, page); page = next_page; } page = self.unused_page; while (page) { DynamicArenaPage* next_page = page.prev_arena; - allocator::free(self.backing_allocator, page.memory); - allocator::free(self.backing_allocator, page); + alloc::free(self.backing_allocator, page.memory); + alloc::free(self.backing_allocator, page); page = next_page; } self.page = null; @@ -61,14 +61,14 @@ struct DynamicArenaPage @local { void* memory; void* prev_arena; - usz total; - usz used; + sz total; + sz used; void* current_stack_ptr; } struct DynamicArenaChunk @local { - usz size; + sz size; } <* @@ -80,7 +80,7 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic DynamicArenaPage* current_page = self.page; if (ptr == current_page.current_stack_ptr) { - current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory); + current_page.used = (sz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory); } current_page.current_stack_ptr = null; } @@ -91,26 +91,26 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic @require self.page != null : `tried to realloc pointer on invalid allocator` @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic +fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, sz size, sz alignment) @dynamic { DynamicArenaPage* current_page = self.page; alignment = alignment_for_allocation(alignment); - usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX; - usz old_size = *old_size_ptr; + sz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX; + sz old_size = *old_size_ptr; // We have the old pointer and it's correctly aligned. if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment)) { *old_size_ptr = size; if (current_page.current_stack_ptr == old_pointer) { - current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory); + current_page.used = (sz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory); } return old_pointer; } if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment)) { assert(size > old_size); - usz add_size = size - old_size; + sz add_size = size - old_size; if (add_size + current_page.used > current_page.total) break REUSE; *old_size_ptr = size; current_page.used += add_size; @@ -144,7 +144,7 @@ fn void DynamicArenaAllocator.reset(&self) @require !alignment || math::is_power_of_2(alignment) @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? DynamicArenaAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); DynamicArenaPage* page = self.page; @@ -163,13 +163,13 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type ptr = _alloc_new(self, size, alignment)!; break SET_DONE; } - void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment); - usz new_used = start - page.memory + size; + void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk::size, alignment); + sz new_used = start - page.memory + size; if ALLOCATE_NEW: (new_used > page.total) { if ((page = self.unused_page)) { - start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment); + start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk::size, alignment); new_used = start + size - page.memory; if (page.total >= new_used) { @@ -197,21 +197,21 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type @require size > 0 @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? _alloc_new(DynamicArenaAllocator* self, usz size, usz alignment) @local +fn void*? _alloc_new(DynamicArenaAllocator* self, sz size, sz alignment) @local { // First, make sure that we can align it, extending the page size if needed. - usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment)); - assert(page_size > size + DynamicArenaChunk.sizeof); + sz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk::size + alignment, alignment)); + assert(page_size > size + DynamicArenaChunk::size); // Grab the page without alignment (we do it ourselves) - void* mem = allocator::malloc_try(self.backing_allocator, page_size)!; - DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage); + void* mem = alloc::malloc_try(self.backing_allocator, page_size)!; + DynamicArenaPage*? page = alloc::new_try(self.backing_allocator, DynamicArenaPage); if (catch err = page) { - allocator::free(self.backing_allocator, mem); + alloc::free(self.backing_allocator, mem); return err~; } page.memory = mem; - void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment); + void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk::size, alignment); assert(mem_start + size < mem + page_size); DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1; chunk.size = size; diff --git a/test/stdlib/src/core/allocators/heap_allocator.c3 b/test/stdlib/src/core/allocators/heap_allocator.c3 index aa91b9b..4d8e87c 100644 --- a/test/stdlib/src/core/allocators/heap_allocator.c3 +++ b/test/stdlib/src/core/allocators/heap_allocator.c3 @@ -2,7 +2,7 @@ // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. -module std::core::mem::allocator; +module std::core::mem::allocators; import std::math; <* @@ -28,7 +28,7 @@ fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator) self.free_list = null; } -fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? SimpleHeapAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { if (init_type == ZERO) { @@ -37,7 +37,7 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_alloc, size, alignment) : simple_alloc_alloc(self, size); } -fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic +fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, sz size, sz alignment) @dynamic { return alignment > 0 ? @aligned_realloc_fn(self, simple_alloc_calloc, simple_alloc_free, old_pointer, size, alignment) @@ -59,28 +59,28 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn <* @require old_pointer && bytes > 0 *> -fn void*? simple_alloc_realloc(SimpleHeapAllocator* self, void* old_pointer, usz bytes) @local +fn void*? simple_alloc_realloc(SimpleHeapAllocator* self, void* old_pointer, sz bytes) @local { // Find the block header. Header* block = (Header*)old_pointer - 1; if (block.size >= bytes) return old_pointer; void* new = simple_alloc_alloc(self, bytes)!; - usz max_to_copy = math::min(block.size, bytes); + sz max_to_copy = math::min(block.size, bytes); mem::copy(new, old_pointer, max_to_copy); simple_alloc_free(self, old_pointer); return new; } -fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, usz bytes) @local +fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, sz bytes) @local { void* data = simple_alloc_alloc(self, bytes)!; mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT); return data; } -fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local +fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, sz bytes) @local { - usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT); + sz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT); if (!self.free_list) { simple_alloc_add_block(self, aligned_bytes)!; @@ -92,7 +92,7 @@ fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local { switch { - case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64: + case current.size >= aligned_bytes && current.size <= aligned_bytes + Header::size + 64: if (current == previous) { self.free_list = current.next; @@ -104,8 +104,8 @@ fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local current.next = null; return current + 1; case current.size > aligned_bytes: - Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof); - unallocated.size = current.size - aligned_bytes - Header.sizeof; + Header* unallocated = (Header*)((char*)current + aligned_bytes + Header::size); + unallocated.size = current.size - aligned_bytes - Header::size; unallocated.next = current.next; if (current == self.free_list) { @@ -127,12 +127,12 @@ fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local return simple_alloc_alloc(self, aligned_bytes); } -fn void? simple_alloc_add_block(SimpleHeapAllocator* self, usz aligned_bytes) @local +fn void? simple_alloc_add_block(SimpleHeapAllocator* self, sz aligned_bytes) @local { assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes); - char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!; + char[] result = self.alloc_fn(aligned_bytes + Header::size)!; Header* new_block = (Header*)result.ptr; - new_block.size = result.len - Header.sizeof; + new_block.size = result.len - Header::size; new_block.next = null; simple_alloc_free(self, new_block + 1); } @@ -175,7 +175,7 @@ fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local if (current == (Header*)((char*)(block + 1) + block.size)) { // Merge - block.size += current.size + Header.sizeof; + block.size += current.size + Header::size; block.next = current.next; } else @@ -194,7 +194,7 @@ fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local // Prev adjacent? if (block == (Header*)((char*)(prev + 1) + prev.size)) { - prev.size += block.size + Header.sizeof; + prev.size += block.size + Header::size; prev.next = block.next; } else @@ -210,7 +210,7 @@ union Header @local struct { Header* next; - usz size; + sz size; } - usz align; + sz align; } diff --git a/test/stdlib/src/core/allocators/libc_allocator.c3 b/test/stdlib/src/core/allocators/libc_allocator.c3 index 2de1a2c..293a5cc 100644 --- a/test/stdlib/src/core/allocators/libc_allocator.c3 +++ b/test/stdlib/src/core/allocators/libc_allocator.c3 @@ -1,7 +1,7 @@ // Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. -module std::core::mem::allocator @if(env::LIBC); +module std::core::mem::allocators @if(env::LIBC); import std::io; import libc; @@ -11,51 +11,51 @@ import libc; typedef LibcAllocator (Allocator) = uptr; const LibcAllocator LIBC_ALLOCATOR = {}; -module std::core::mem::allocator @if(env::POSIX); +module std::core::mem::allocators @if(env::POSIX); import std::os; import libc; -fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic +fn void*? LibcAllocator.acquire(&self, sz bytes, AllocInitType init_type, sz alignment) @dynamic { if (init_type == ZERO) { void* data @noinit; if (alignment > mem::DEFAULT_MEM_ALIGNMENT) { - if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~; + if (posix::posix_memalign(&data, (usz)alignment, (usz)bytes)) return mem::OUT_OF_MEMORY~; mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT); return data; } - return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~; + return libc::calloc(1, (usz)bytes) ?: mem::OUT_OF_MEMORY~; } else { void* data @noinit; if (alignment > mem::DEFAULT_MEM_ALIGNMENT) { - if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~; + if (posix::posix_memalign(&data, (usz)alignment, (usz)bytes)) return mem::OUT_OF_MEMORY~; } else { - if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY~; + if (!(data = libc::malloc((usz)bytes))) return mem::OUT_OF_MEMORY~; } $if env::TESTING: - for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; + for (sz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; $endif return data; } } -fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic +fn void*? LibcAllocator.resize(&self, void* old_ptr, sz new_bytes, sz alignment) @dynamic { - if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; + if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, (usz)new_bytes) ?: mem::OUT_OF_MEMORY~; // Try realloc, even though it might be unaligned. - void* new_ptr = libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~!; + void* new_ptr = libc::realloc(old_ptr, (usz)new_bytes) ?: mem::OUT_OF_MEMORY~!; // If it's aligned then we're done! uptr ptr_val = (uptr)new_ptr; - if (ptr_val & (alignment - 1) == 0) return new_ptr; + if (ptr_val & ((uptr)alignment - 1u) == 0) return new_ptr; // We failed, so we need to use memalign // We will free new_ptr before we exit. @@ -63,7 +63,7 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen // Create a pointer which is sure to be aligned. void* aligned_ptr; - if (posix::posix_memalign(&aligned_ptr, alignment, new_bytes)) + if (posix::posix_memalign(&aligned_ptr, (usz)alignment, (usz)new_bytes)) { return mem::OUT_OF_MEMORY~; } @@ -83,31 +83,31 @@ module std::core::mem::allocator @if(env::WIN32); import std::os::win32; import libc; -fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic +fn void*? LibcAllocator.acquire(&self, sz bytes, AllocInitType init_type, sz alignment) @dynamic { if (init_type == ZERO) { if (alignment > 0) { - return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~; + return win32::_aligned_recalloc(null, 1, (usz)bytes, (usz)alignment) ?: mem::OUT_OF_MEMORY~; } - return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~; + return libc::calloc(1, (usz)bytes) ?: mem::OUT_OF_MEMORY~; } - void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes); + void* data = alignment > 0 ? win32::_aligned_malloc((usz)bytes, (usz)alignment) : libc::malloc((usz)bytes); if (!data) return mem::OUT_OF_MEMORY~; $if env::TESTING: - for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; + for (sz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; $endif return data; } -fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic +fn void*? LibcAllocator.resize(&self, void* old_ptr, sz new_bytes, sz alignment) @dynamic { if (alignment) { - return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY~; + return win32::_aligned_realloc(old_ptr, (usz)new_bytes, (usz)alignment) ?: mem::OUT_OF_MEMORY~; } - return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; + return libc::realloc(old_ptr, (usz)new_bytes) ?: mem::OUT_OF_MEMORY~; } fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic @@ -123,11 +123,11 @@ fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::LIBC); import libc; -fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic +fn void*? LibcAllocator.acquire(&self, sz bytes, AllocInitType init_type, sz alignment) @dynamic { if (init_type == ZERO) { - void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1); + void* data = alignment ? @aligned_alloc(fn void*(sz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1); return data ?: mem::OUT_OF_MEMORY~; } else @@ -135,21 +135,21 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes); if (!data) return mem::OUT_OF_MEMORY~; $if env::TESTING: - for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; + for (sz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; $endif return data; } } -fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic +fn void*? LibcAllocator.resize(&self, void* old_ptr, sz new_bytes, sz alignment) @dynamic { if (alignment) { - void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!; + void* data = @aligned_realloc(fn void*(sz bytes) => libc::malloc((usz)bytes), libc::free, old_ptr, new_bytes, alignment)!!; return data ?: mem::OUT_OF_MEMORY~; } - return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; + return libc::realloc(old_ptr, (usz)new_bytes) ?: mem::OUT_OF_MEMORY~; } diff --git a/test/stdlib/src/core/allocators/null_allocator.c3 b/test/stdlib/src/core/allocators/null_allocator.c3 new file mode 100644 index 0000000..5de214a --- /dev/null +++ b/test/stdlib/src/core/allocators/null_allocator.c3 @@ -0,0 +1,18 @@ +module std::core::mem::allocators; + +const NullAllocator NULL_ALLOCATOR = {}; +typedef NullAllocator (Allocator) = uptr; + +fn void*? NullAllocator.acquire(&self, sz bytes, AllocInitType init_type, sz alignment) @dynamic +{ + return mem::OUT_OF_MEMORY~; +} + +fn void*? NullAllocator.resize(&self, void* old_ptr, sz new_bytes, sz alignment) @dynamic +{ + return mem::OUT_OF_MEMORY~; +} + +fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic +{ +} diff --git a/test/stdlib/src/core/allocators/on_stack_allocator.c3 b/test/stdlib/src/core/allocators/on_stack_allocator.c3 index af9d694..6851b31 100644 --- a/test/stdlib/src/core/allocators/on_stack_allocator.c3 +++ b/test/stdlib/src/core/allocators/on_stack_allocator.c3 @@ -1,4 +1,4 @@ -module std::core::mem::allocator; +module std::core::mem::allocators; import std::math; <* The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory @@ -13,7 +13,7 @@ struct OnStackAllocator (Allocator) { Allocator backing_allocator; char[] data; - usz used; + sz used; OnStackAllocatorExtraChunk* chunk; } @@ -43,15 +43,15 @@ fn void OnStackAllocator.free(&self) { if (chunk.is_aligned) { - allocator::free_aligned(self.backing_allocator, chunk.data); + alloc::free_aligned(self.backing_allocator, chunk.data); } else { - allocator::free(self.backing_allocator, chunk.data); + alloc::free(self.backing_allocator, chunk.data); } void* old = chunk; chunk = chunk.prev; - allocator::free(self.backing_allocator, old); + alloc::free(self.backing_allocator, old); } self.chunk = null; self.used = 0; @@ -59,7 +59,7 @@ fn void OnStackAllocator.free(&self) struct OnStackAllocatorHeader { - usz size; + sz size; char[*] data; } @@ -87,7 +87,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local if (chunk.data == ptr) { *addr = chunk.prev; - allocator::free(a.backing_allocator, chunk); + alloc::free(a.backing_allocator, chunk); return; } addr = &chunk.prev; @@ -112,7 +112,7 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a @require old_pointer != null @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` *> -fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic +fn void*? OnStackAllocator.resize(&self, void* old_pointer, sz size, sz alignment) @dynamic { if (!allocation_in_stack_mem(self, old_pointer)) { @@ -121,8 +121,8 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!; } - OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof; - usz old_size = header.size; + OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader::size; + sz old_size = header.size; void* mem = self.acquire(size, NO_ZERO, alignment)!; mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); return mem; @@ -132,27 +132,27 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` @require size > 0 *> -fn void*? OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? OnStackAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { bool aligned = alignment > 0; alignment = alignment_for_allocation(alignment); - usz total_len = self.data.len; + sz total_len = self.data.len; void* start_mem = self.data.ptr; - void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ; + void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader::size ; void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); - usz end = (usz)(mem - self.data.ptr) + size; + sz end = (sz)(mem - self.data.ptr) + size; Allocator backing_allocator = self.backing_allocator; if (end > total_len) { - OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!; - defer catch allocator::free(backing_allocator, chunk); + OnStackAllocatorExtraChunk* chunk = alloc::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!; + defer catch alloc::free(backing_allocator, chunk); defer try self.chunk = chunk; *chunk = { .prev = self.chunk, .is_aligned = aligned }; return chunk.data = backing_allocator.acquire(size, init_type, aligned ? alignment : 0)!; } self.used = end; - OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof; + OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader::size; header.size = size; return mem; } diff --git a/test/stdlib/src/core/allocators/temp_allocator.c3 b/test/stdlib/src/core/allocators/temp_allocator.c3 index 19cd747..8980189 100644 --- a/test/stdlib/src/core/allocators/temp_allocator.c3 +++ b/test/stdlib/src/core/allocators/temp_allocator.c3 @@ -1,4 +1,4 @@ -module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP)); +module std::core::mem::allocators @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP)); import std::io, std::math; // This implements the temp allocator. @@ -34,44 +34,44 @@ struct TempAllocator (Allocator) TempAllocatorPage* last_page; TempAllocator* derived; bool allocated; - usz reserve_size; - usz realloc_size; - usz min_size; - usz used; - usz capacity; - usz original_capacity; + sz reserve_size; + sz realloc_size; + sz min_size; + sz used; + sz capacity; + sz original_capacity; char[*] data; } struct TempAllocatorChunk @local { - usz size; + sz size; char[*] data; } -const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u; +const sz PAGE_IS_ALIGNED @local = sz::min; struct TempAllocatorPage { TempAllocatorPage* prev_page; void* start; - usz size; - usz ident; + sz size; + sz ident; char[*] data; } -macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED; +macro sz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED; macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED; <* @require size >= 64 @require realloc_size >= 64 - @require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator." - @require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes" + @require allocator.type != TempAllocator::typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator." + @require min_size > TempAllocator::size + 64 : "Min size must meaningfully hold the data + some bytes" *> -fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size) +fn TempAllocator*? new_temp_allocator(Allocator allocator, sz size, sz reserve = temp_allocator_reserve_size, sz min_size = temp_allocator_min_size, sz realloc_size = temp_allocator_realloc_size) { - TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!; + TempAllocator* temp = alloc::alloc_with_padding(allocator, TempAllocator, size)!; temp.last_page = null; temp.backing_allocator = allocator; temp.used = 0; @@ -86,21 +86,21 @@ fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve <* @require !self.derived *> -fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0) +fn TempAllocator*? TempAllocator.derive_allocator(&self, sz reserve = 0) { if (!reserve) reserve = self.reserve_size; - usz remaining = self.capacity - self.used; + sz remaining = self.capacity - self.used; void* mem @noinit; - usz size @noinit; + sz size @noinit; if (self.min_size + reserve > remaining) { return self.derived = new_temp_allocator(self.backing_allocator, self.realloc_size, self.reserve_size, self.min_size, self.realloc_size)!; } - usz start = mem::aligned_offset(self.used + reserve, mem::DEFAULT_MEM_ALIGNMENT); + sz start = mem::aligned_offset(self.used + reserve, mem::DEFAULT_MEM_ALIGNMENT); void* ptr = &self.data[start]; TempAllocator* temp = (TempAllocator*)ptr; $if env::ADDRESS_SANITIZER: - asan::unpoison_memory_region(ptr, TempAllocator.sizeof); + asan::unpoison_memory_region(ptr, TempAllocator::size); $endif temp.last_page = null; temp.backing_allocator = self.backing_allocator; @@ -110,7 +110,7 @@ fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0) temp.realloc_size = self.realloc_size; temp.allocated = false; temp.derived = null; - temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof; + temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator::size; self.capacity = start; self.derived = temp; return temp; @@ -156,7 +156,7 @@ fn void temp_allocator_destroy(TempAllocator* self) } if (self.allocated) { - allocator::free(self.backing_allocator, self); + alloc::free(self.backing_allocator, self); return; } $if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER: @@ -170,30 +170,30 @@ fn void temp_allocator_destroy(TempAllocator* self) fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic { - usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX); + sz old_size = *(sz*)(old_pointer - DEFAULT_SIZE_PREFIX); if (old_pointer + old_size == &self.data[self.used]) { self.used -= old_size; - asan::poison_memory_region(&self.data[self.used], old_size); + asan::poison_memory_region(&self.data[self.used], (usz)old_size); } } -fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic +fn void*? TempAllocator.resize(&self, void* pointer, sz size, sz alignment) @dynamic { - TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof; - if (chunk.size == (usz)-1) + TempAllocatorChunk *chunk = pointer - TempAllocatorChunk::size; + if (chunk.size == (sz)-1) { assert(self.last_page, "Realloc of non temp pointer"); // First grab the page - TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof; + TempAllocatorPage *page = pointer - TempAllocatorPage::size; return _realloc_page(self, page, size, alignment); } bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used]; if (is_realloc_of_last) { - isz diff = size - chunk.size; + sz diff = size - chunk.size; if (diff == 0) return pointer; if (self.capacity - self.used > diff) { @@ -213,13 +213,13 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d } } void* data = self.acquire(size, NO_ZERO, alignment)!; - usz len_to_copy = chunk.size > size ? size : chunk.size; + sz len_to_copy = chunk.size > size ? size : chunk.size; mem::copy(data, pointer, len_to_copy, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); if (is_realloc_of_last) { - self.used = (uptr)chunk - (uptr)&self.data; + self.used = (sz)((iptr)chunk - (iptr)&self.data); $if env::ADDRESS_SANITIZER: - asan::poison_memory_region(chunk, TempAllocatorChunk.sizeof + chunk.size); + asan::poison_memory_region(chunk, TempAllocatorChunk::size + chunk.size); $endif } return data; @@ -230,24 +230,24 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d @require !alignment || math::is_power_of_2(alignment) @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` *> -fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? TempAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); void* start_mem = &self.data; void* starting_ptr = start_mem + self.used; - void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof); - void* mem = aligned_header_start + TempAllocatorChunk.sizeof; - if (alignment > TempAllocatorChunk.alignof) + void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk::alignment); + void* mem = aligned_header_start + TempAllocatorChunk::size; + if (alignment > TempAllocatorChunk::alignment) { mem = mem::aligned_pointer(mem, alignment); } - usz new_usage = (usz)(mem - start_mem) + size; + sz new_usage = (sz)(mem - start_mem) + size; // Arena allocation, simple! if (new_usage <= self.capacity) { - asan::unpoison_memory_region(starting_ptr, new_usage - self.used); - TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof; + asan::unpoison_memory_region(starting_ptr, (usz)(new_usage - self.used)); + TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk::size; chunk_start.size = size; self.used = new_usage; if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); @@ -261,17 +261,17 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al if (alignment > mem::DEFAULT_MEM_ALIGNMENT) { // This is actually simpler, since it will create the offset for us. - usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment); + sz total_alloc_size = mem::aligned_offset(TempAllocatorPage::size + size, alignment); if (init_type == ZERO) { - mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; + mem = alloc::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } else { - mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; + mem = alloc::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!; } void* start = mem; - mem += mem::aligned_offset(TempAllocatorPage.sizeof, alignment); + mem += mem::aligned_offset(TempAllocatorPage::size, alignment); page = (TempAllocatorPage*)mem - 1; page.start = start; page.size = size | PAGE_IS_ALIGNED; @@ -279,20 +279,20 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al else { // Here we might need to pad - usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT); - usz total_alloc_size = padded_header_size + size; + sz padded_header_size = mem::aligned_offset(TempAllocatorPage::size, mem::DEFAULT_MEM_ALIGNMENT); + sz total_alloc_size = padded_header_size + size; void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!; // Find the page. - page = alloc + padded_header_size - TempAllocatorPage.sizeof; - assert(mem::ptr_is_aligned(page, TempAllocator.alignof)); + page = alloc + padded_header_size - TempAllocatorPage::size; + assert(mem::ptr_is_aligned(page, TempAllocator::alignment)); assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT)); page.start = alloc; page.size = size; } // Mark it as a page - page.ident = ~(usz)0; + page.ident = ~(sz)0; // Hook up the page. page.prev_page = self.last_page; self.last_page = page; @@ -306,7 +306,7 @@ fn void? _free_page(TempAllocator* self, TempAllocatorPage* page) @inline @local return self.backing_allocator.release(mem, page.is_aligned()); } -fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, usz alignment) @inline @local +fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, sz size, sz alignment) @inline @local { // Then the actual start pointer: void* real_pointer = page.start; @@ -319,7 +319,7 @@ fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, pointer_to_prev = &((*pointer_to_prev).prev_page); } *pointer_to_prev = page.prev_page; - usz page_size = page.pagesize(); + sz page_size = page.pagesize(); // Clear on size > original size. void* data = self.acquire(size, NO_ZERO, alignment)!; if (page_size > size) page_size = size; @@ -328,7 +328,7 @@ fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, return data; } -module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP)); +module std::core::mem::allocators @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP)); import std::math; @@ -339,13 +339,13 @@ tlocal VmemOptions temp_allocator_default_options = { }; -fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size) +fn TempAllocator*? new_temp_allocator(Allocator allocator, sz size, sz reserve = temp_allocator_reserve_size, sz min_size = temp_allocator_min_size, sz realloc_size = temp_allocator_realloc_size) { Vmem mem; - TempAllocator* t = allocator::new(allocator, TempAllocator); - defer catch allocator::free(allocator, t); - t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB, - reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0, + TempAllocator* t = alloc::new(allocator, TempAllocator); + defer catch alloc::free(allocator, t); + t.vmem.init(preferred_size: sz::size > 4 ? 4 * mem::GB : 512 * mem::MB, + reserve_page_size: sz::size > 4 ? 256 * mem::KB : 0, options: temp_allocator_default_options)!; t.allocator = allocator; return t; @@ -363,12 +363,12 @@ struct TempAllocator (Allocator) @require !alignment || math::is_power_of_2(alignment) @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` *> -fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? TempAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { return self.vmem.acquire(size, init_type, alignment) @inline; } -fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0) +fn TempAllocator*? TempAllocator.derive_allocator(&self, sz reserve = 0) { if (self.derived) return self.derived; return self.derived = new_temp_allocator(self.allocator, 0)!; @@ -395,10 +395,10 @@ fn void _destroy(TempAllocator* self) @local if (!child) return; _destroy(child); self.vmem.free() @inline; - allocator::free(self.allocator, self) @inline; + alloc::free(self.allocator, self) @inline; } -fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic +fn void*? TempAllocator.resize(&self, void* pointer, sz size, sz alignment) @dynamic { return self.vmem.resize(pointer, size, alignment) @inline; } @@ -407,3 +407,23 @@ fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic { self.vmem.release(old_pointer, b) @inline; } + +module std::core::mem::allocators; + +typedef LazyTempAllocator (Allocator) @private = uptr; + +fn void*? LazyTempAllocator.acquire(&self, sz bytes, AllocInitType init_type, sz alignment) @dynamic +{ + if (!top_temp) create_temp_allocator_on_demand(); + return top_temp.acquire(bytes, init_type, alignment); +} + +fn void*? LazyTempAllocator.resize(&self, void* old_ptr, sz new_bytes, sz alignment) @dynamic +{ + if (!top_temp) create_temp_allocator_on_demand(); + return top_temp.resize(old_ptr, new_bytes, alignment); +} + +fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic +{ +} diff --git a/test/stdlib/src/core/allocators/tracking_allocator.c3 b/test/stdlib/src/core/allocators/tracking_allocator.c3 index b3049ff..aee0f8a 100644 --- a/test/stdlib/src/core/allocators/tracking_allocator.c3 +++ b/test/stdlib/src/core/allocators/tracking_allocator.c3 @@ -2,14 +2,14 @@ // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. -module std::core::mem::allocator; +module std::core::mem::allocators; import std::collections, std::io, std::os::backtrace; const MAX_BACKTRACE = 16; struct Allocation { void* ptr; - usz size; + sz size; void*[MAX_BACKTRACE] backtrace; } @@ -26,10 +26,10 @@ struct TrackingAllocator (Allocator) { Allocator inner_allocator; AllocMap map; - usz mem_total; - usz allocs_total; - usz usage; - usz max_usage; + sz mem_total; + sz allocs_total; + sz usage; + sz max_usage; } <* @@ -55,9 +55,9 @@ fn void TrackingAllocator.free(&self) <* @return "the total allocated memory not yet freed." *> -fn usz TrackingAllocator.allocated(&self) => @pool() +fn sz TrackingAllocator.allocated(&self) => @pool() { - usz allocated = 0; + sz allocated = 0; foreach (&allocation : self.map.tvalues()) allocated += allocation.size; return allocated; } @@ -65,17 +65,17 @@ fn usz TrackingAllocator.allocated(&self) => @pool() <* @return "the total memory allocated (freed or not)." *> -fn usz TrackingAllocator.total_allocated(&self) => self.mem_total; +fn sz TrackingAllocator.total_allocated(&self) => self.mem_total; <* @return "the total number of allocations (freed or not)." *> -fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total; +fn sz TrackingAllocator.total_allocation_count(&self) => self.allocs_total; <* @return "the maximum amount of memory allocated" *> -fn usz TrackingAllocator.max_allocated(&self) => self.max_usage; +fn sz TrackingAllocator.max_allocated(&self) => self.max_usage; fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator) { @@ -85,9 +85,9 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator) <* @return "the number of non-freed allocations." *> -fn usz TrackingAllocator.allocation_count(&self) => self.map.count; +fn sz TrackingAllocator.allocation_count(&self) => self.map.count; -fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? TrackingAllocator.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { void* data = self.inner_allocator.acquire(size, init_type, alignment)!; self.allocs_total++; @@ -100,7 +100,7 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us return data; } -fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic +fn void*? TrackingAllocator.resize(&self, void* old_pointer, sz size, sz alignment) @dynamic { void* data = self.inner_allocator.resize(old_pointer, size, alignment)!; self.usage -= self.map[(uptr)old_pointer]!!.size; @@ -117,7 +117,7 @@ fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz align fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic { - usz? old_size = self.map[(uptr)old_pointer].size; + sz? old_size = self.map[(uptr)old_pointer].size; if (catch self.map.remove((uptr)old_pointer)) { unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer); @@ -141,8 +141,8 @@ fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout() fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool() { - usz total = 0; - usz entries = 0; + sz total = 0; + sz entries = 0; bool leaks = false; Allocation[] allocs = self.map.tvalues(); @@ -204,7 +204,7 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool() continue; } BacktraceList backtraces = {}; - usz end = MAX_BACKTRACE; + sz end = MAX_BACKTRACE; foreach (j, val : allocation.backtrace) { if (!val) diff --git a/test/stdlib/src/core/allocators/vmem.c3 b/test/stdlib/src/core/allocators/vmem.c3 index dacbbb0..e8f9f0c 100644 --- a/test/stdlib/src/core/allocators/vmem.c3 +++ b/test/stdlib/src/core/allocators/vmem.c3 @@ -1,7 +1,5 @@ -module std::core::mem::allocator @if(env::POSIX || env::WIN32); +module std::core::mem::allocators @if(env::POSIX || env::WIN32); import std::math, std::os::posix, libc, std::bits; -import std::core::mem; -import std::core::env; @@ -12,11 +10,11 @@ faultdef VMEM_RESERVE_FAILED; struct Vmem (Allocator) { VirtualMemory memory; - usz allocated; - usz pagesize; - usz page_pot; - usz last_page; - usz high_water; + sz allocated; + sz pagesize; + sz page_pot; + sz last_page; + sz high_water; VmemOptions options; } @@ -38,9 +36,9 @@ bitstruct VmemOptions : int @require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB" @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED *> -fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0) +fn void? Vmem.init(&self, sz preferred_size, sz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, sz min_size = 0) { - static usz page_size = 0; + static sz page_size = 0; if (!page_size) page_size = mem::os_pagesize(); if (page_size < reserve_page_size) page_size = reserve_page_size; preferred_size = mem::aligned_offset(preferred_size, page_size); @@ -83,22 +81,22 @@ fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOpt @require size > 0 @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic +fn void*? Vmem.acquire(&self, sz size, AllocInitType init_type, sz alignment) @dynamic { alignment = alignment_for_allocation(alignment); - usz total_len = self.memory.size; + sz total_len = self.memory.size; if (size > total_len) return mem::INVALID_ALLOC_SIZE~; void* start_mem = self.memory.ptr; - void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof; + void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader::size; void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); - usz after = (usz)(mem - start_mem) + size; + sz after = (sz)(mem - start_mem) + size; if (after > total_len) return mem::OUT_OF_MEMORY~; if (init_type == ZERO && self.high_water <= self.allocated) { init_type = NO_ZERO; } protect(self, after)!; - VmemHeader* header = mem - VmemHeader.sizeof; + VmemHeader* header = mem - VmemHeader::size; header.size = size; if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT); return mem; @@ -106,7 +104,7 @@ fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) fn bool Vmem.owns_pointer(&self, void* ptr) @inline { - return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size; + return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + (usz)self.memory.size; } <* Implements the Allocator interface method. @@ -117,13 +115,13 @@ fn bool Vmem.owns_pointer(&self, void* ptr) @inline @require size > 0 @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY *> -fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic +fn void*? Vmem.resize(&self, void *old_pointer, sz size, sz alignment) @dynamic { if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~; alignment = alignment_for_allocation(alignment); assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated); - VmemHeader* header = old_pointer - VmemHeader.sizeof; - usz old_size = header.size; + VmemHeader* header = old_pointer - VmemHeader::size; + sz old_size = header.size; if (old_size == size) return old_pointer; // Do last allocation and alignment match? if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment)) @@ -134,7 +132,7 @@ fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynami } else { - usz allocated = self.allocated + size - old_size; + sz allocated = self.allocated + size - old_size; if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~; protect(self, allocated)!; } @@ -164,15 +162,15 @@ fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynami fn void Vmem.release(&self, void* ptr, bool) @dynamic { assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr); - VmemHeader* header = ptr - VmemHeader.sizeof; + VmemHeader* header = ptr - VmemHeader::size; // Reclaim memory if it's the last element. if (ptr + header.size == self.memory.ptr + self.allocated) { - unprotect(self, self.allocated - header.size - VmemHeader.sizeof); + unprotect(self, self.allocated - header.size - VmemHeader::size); } } -fn usz Vmem.mark(&self) +fn sz Vmem.mark(&self) { return self.allocated; } @@ -180,7 +178,7 @@ fn usz Vmem.mark(&self) <* @require mark <= self.allocated : "Invalid mark" *> -fn void Vmem.reset(&self, usz mark) +fn void Vmem.reset(&self, sz mark) { if (mark == self.allocated) return; unprotect(self, mark); @@ -203,20 +201,20 @@ fn void Vmem.free(&self) struct VmemHeader @local { - usz size; + sz size; char[*] data; } -macro void? protect(Vmem* mem, usz after) @local +macro void? protect(Vmem* mem, sz after) @local { - usz shift = mem.page_pot; - usz page_after = (after + mem.pagesize - 1) >> shift; - usz last_page = mem.last_page; + sz shift = mem.page_pot; + sz page_after = (after + mem.pagesize - 1) >> shift; + sz last_page = mem.last_page; bool over_high_water = mem.high_water < after; if (page_after > last_page) { - usz page_start = last_page << shift; - usz page_len = (page_after - last_page) << shift; + sz page_start = last_page << shift; + sz page_len = (page_after - last_page) << shift; mem.memory.commit(page_start, page_len)!; if (mem.options.protect_unused_pages || over_high_water) { @@ -231,11 +229,11 @@ macro void? protect(Vmem* mem, usz after) @local if (over_high_water) mem.high_water = after; } -macro void unprotect(Vmem* mem, usz after) @local +macro void unprotect(Vmem* mem, sz after) @local { - usz shift = mem.page_pot; - usz last_page = mem.last_page; - usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift; + sz shift = mem.page_pot; + sz last_page = mem.last_page; + sz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift; $if env::ADDRESS_SANITIZER: asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after); $else @@ -246,8 +244,8 @@ macro void unprotect(Vmem* mem, usz after) @local $endif if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page) { - usz start = page_after << shift; - usz len = (last_page - page_after) << shift; + sz start = page_after << shift; + sz len = (last_page - page_after) << shift; if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false); if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED); } diff --git a/test/stdlib/src/core/ansi.c3 b/test/stdlib/src/core/ansi.c3 index b0d6996..833f5d5 100644 --- a/test/stdlib/src/core/ansi.c3 +++ b/test/stdlib/src/core/ansi.c3 @@ -63,7 +63,7 @@ struct AnsiColor (Printable) bool bg; } -fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic +fn sz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic { return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b); } @@ -122,44 +122,3 @@ macro String color(uint $rgb, bool $bg = false) @const return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF); } -<* - 24-bit color code rgb - - @require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value` - @return `the string char for the given foreground color` -*> -fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead") -{ - return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg); -} - -<* - 24-bit color code rgb - - @require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value` - @return `the string char for the given foreground color` -*> -fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead") -{ - return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg); -} - -<* - 24-bit color code rgb - - @return `the string char for the given foreground color` -*> -fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead") -{ - return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b); -} - -<* - 24-bit color code rgb - - @return `the string char for the given foreground color` -*> -fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead") -{ - return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b); -} diff --git a/test/stdlib/src/core/array.c3 b/test/stdlib/src/core/array.c3 index e8346ce..d43a6e9 100644 --- a/test/stdlib/src/core/array.c3 +++ b/test/stdlib/src/core/array.c3 @@ -6,7 +6,7 @@ import std::collections::pair, std::io; @param [in] array @param [in] element - @require $kindof(array) == SLICE || $kindof(array) == ARRAY + @require @kindof(array) == SLICE || @kindof(array) == ARRAY @require @typematch(array[0], element) : "array and element must have the same type" *> macro bool contains(array, element) @@ -24,12 +24,13 @@ macro bool contains(array, element) @param [in] array @param [in] element - @require $kindof(array) == SLICE || $kindof(array) == ARRAY + @require @kindof(array) == SLICE || @kindof(array) == ARRAY @require @typematch(array[0], element) : "array and element must have the same type" + @ensure return >= 0 @return "the first index of the element" @return? NOT_FOUND *> -macro usz? index_of(array, element) +macro sz? index_of(array, element) { foreach (i, &e : array) { @@ -48,16 +49,16 @@ macro usz? index_of(array, element) @param xlen : "The length of the slice in x, defaults to the length of the array" @param ylen : "The length of the slice in y, defaults to the length of the array" @return "A Slice2d from the array" - @require $kindof(array_ptr) == POINTER - @require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY - @require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == ARRAY + @require @kindof(array_ptr) == POINTER + @require @kindof(*array_ptr) == VECTOR || @kindof(*array_ptr) == ARRAY + @require @kindof((*array_ptr)[0]) == VECTOR || @kindof((*array_ptr)[0]) == ARRAY *> macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0) { - if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen; - if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen; + if (xlen < 1) xlen = $typeof((*array_ptr)[0])::len + xlen; + if (ylen < 1) ylen = $typeof((*array_ptr))::len + ylen; var $ElementType = $typeof((*array_ptr)[0][0]); - return (Slice2d{$ElementType}) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen }; + return (Slice2d{$ElementType}) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0])::len, y, ylen, x, xlen }; } @@ -66,10 +67,11 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0) @param [in] array @param [in] element + @ensure return >= 0 @return "the last index of the element" @return? NOT_FOUND *> -macro usz? rindex_of(array, element) +macro sz? rindex_of(array, element) { foreach_r (i, &e : array) { @@ -84,23 +86,23 @@ macro usz? rindex_of(array, element) @param [in] arr1 @param [in] arr2 - @param [&inout] allocator : "The allocator to use, default is the heap allocator" - @require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY - @require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY + @param [&inout] allocator : "The allocator to use" + @require @kindof(arr1) == SLICE || @kindof(arr1) == ARRAY + @require @kindof(arr2) == SLICE || @kindof(arr2) == ARRAY @require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type" @ensure result.len == arr1.len + arr2.len *> macro concat(Allocator allocator, arr1, arr2) @nodiscard { var $Type = $typeof(arr1[0]); - $Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len); + $Type[] result = alloc::alloc_array(allocator, $Type, arr1.len + arr2.len); if (arr1.len > 0) { - mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof); + mem::copy(result.ptr, &arr1[0], arr1.len * $Type::size, $Type::alignment, $Type::alignment); } if (arr2.len > 0) { - mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof); + mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type::size, $Type::alignment, $Type::alignment); } return result; } @@ -111,8 +113,8 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard @param [in] arr1 @param [in] arr2 - @require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY - @require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY + @require @kindof(arr1) == SLICE || @kindof(arr1) == ARRAY + @require @kindof(arr2) == SLICE || @kindof(arr2) == ARRAY @require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type" @ensure return.len == arr1.len + arr2.len *> @@ -192,7 +194,7 @@ macro @product(array, identity_value = 1) <* Applies a given predicate function to each element of an array and returns a new - array of `usz` values, each element representing an index within the original array + array of `sz` values, each element representing an index within the original array where the predicate returned `true`. The `.len` value of the returned array can also be used to quickly identify how many @@ -214,10 +216,10 @@ macro @product(array, identity_value = 1) @require @is_valid_list(array) : "Expected a valid list" @require $defined($typefrom(@predicate_fn(array)) p = #predicate) *> -macro usz[] @indices_of(Allocator allocator, array, #predicate) +macro sz[] @indices_of(Allocator allocator, array, #predicate) { - usz[] results = allocator::new_array(allocator, usz, lengthof(array)); - usz matches; + sz[] results = alloc::new_array(allocator, sz, lengthof(array)); + sz matches; $typefrom(@predicate_fn(array)) $predicate = #predicate; foreach (index, element : array) @@ -237,7 +239,7 @@ macro usz[] @indices_of(Allocator allocator, array, #predicate) @require @is_valid_list(array) : "Expected a valid list" @require $defined($typefrom(@predicate_fn(array)) p = #predicate) *> -macro usz[] @tindices_of(array, #predicate) +macro sz[] @tindices_of(array, #predicate) { return @indices_of(tmem, array, #predicate); } @@ -266,12 +268,12 @@ macro @filter(Allocator allocator, array, #predicate) @nodiscard { var $InnerType = $typeof(array[0]); - usz[] matched_indices = @indices_of(allocator, array, #predicate); - defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call + sz[] matched_indices = @indices_of(allocator, array, #predicate); + defer alloc::free(allocator, matched_indices.ptr); // can free this upon leaving this call if (!matched_indices.len) return ($InnerType[]){}; - $InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len); + $InnerType[] result = alloc::new_array(allocator, $InnerType, matched_indices.len); foreach (i, index : matched_indices) result[i] = array[index]; @@ -367,9 +369,9 @@ macro odd(Allocator allocator, array) *> fn Type[] unlace_impl(Allocator allocator, Type[] array) @private { - usz new_len = array.len / 2 + (array.len % 2 == 0 ? 0 : 1); + sz new_len = array.len / 2 + (array.len % 2 == 0 ? 0 : 1); if (new_len == 0) return (Type[]){}; - Type[] new_array = allocator::new_array(allocator, Type, new_len); + Type[] new_array = alloc::new_array(allocator, Type, new_len); foreach (x, &new : new_array) *new = types::implements_copy(Type) ??? array[x * 2].copy(allocator) : array[x * 2]; return new_array[:new_len]; } @@ -444,7 +446,7 @@ macro unlace(Allocator allocator, array, left, right) where the `left` array is the shorter iterable, will put 7 into that lambda in each place where `left` is being filled in during the zip operation. - @param [&inout] allocator : "The allocator to use; default is the heap allocator." + @param [&inout] allocator : "The allocator to use." @param [in] left : "The left-side array. These items will be placed as the First in each Pair" @param [in] right : "The right-side array. These items will be placed as the Second in each Pair" @param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively." @@ -462,15 +464,15 @@ macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) var $Type = Pair { $LeftType, $RightType }; bool $is_op = $defined(#operation); $if $is_op: - $Type = $typeof(#operation).returns; + $Type = $typeof(#operation)::returns; $endif - usz left_len = lengthof(left); - usz right_len = lengthof(right); + sz left_len = lengthof(left); + sz right_len = lengthof(right); $LeftType left_fill; $RightType right_fill; - usz result_len = min(left_len, right_len); + sz result_len = min(left_len, right_len); $if $defined(fill_with): switch { @@ -493,7 +495,7 @@ macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) if (result_len == 0) return ($Type[]){}; - $Type[] result = allocator::alloc_array(allocator, $Type, result_len); + $Type[] result = alloc::alloc_array(allocator, $Type, result_len); foreach (idx, &item : result) { @@ -576,12 +578,12 @@ module std::core::array @private; macro typeid @predicate_fn(#array) @const { - return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid; + return $typeof(fn bool ($typeof(#array[0]) a, sz index = 0) => true)::typeid; } macro typeid @reduce_fn(#array, #identity) @const { - return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i); + return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, sz index = 0) => i); } macro typeid @zip_into_fn(#left, #right) @const @@ -594,7 +596,7 @@ macro bool @is_valid_operation(#left, #right, #operation = ...) @const $switch: $case !$defined(#operation): return true; - $case $kindof(#operation) != FUNC: + $case @kindof(#operation) != FUNC: return false; $default: return $defined(#operation(#left[0], #right[0])); @@ -611,8 +613,8 @@ macro bool @is_valid_fill(left, right, fill_with = ...) $if !$defined(fill_with): return true; $else - usz left_len = lengthof(left); - usz right_len = lengthof(right); + sz left_len = lengthof(left); + sz right_len = lengthof(right); if (left_len == right_len) return true; return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with); $endif diff --git a/test/stdlib/src/core/bitorder.c3 b/test/stdlib/src/core/bitorder.c3 index 771bb77..ce8da39 100644 --- a/test/stdlib/src/core/bitorder.c3 +++ b/test/stdlib/src/core/bitorder.c3 @@ -140,29 +140,29 @@ macro void store_le(void* dst, value) <* @require @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char" @require is_bitorder($Type) : "type must be a bitorder integer" - @require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value" + @require $defined(*bytes) ||| $defined(bytes[:$Type::size]) : "Data is too short to contain value" *> macro read(bytes, $Type) { char *ptr; - $switch $kindof(bytes): + $switch @kindof(bytes): $case POINTER: ptr = bytes; $default: ptr = bytes[..].ptr; $endswitch - return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $Type).val; + return bitcast(mem::load((char[$Type::size]*)ptr, $align: 1), $Type).val; } <* @require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char" @require is_bitorder($Type) : "type must be a bitorder integer" - @require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value" + @require $defined(*bytes) ||| $defined(bytes[:$Type::size]) : "Data is not sufficent to hold value" *> macro write(x, bytes, $Type) { char *ptr; - $switch $kindof(bytes): + $switch @kindof(bytes): $case POINTER: ptr = bytes; $default: @@ -196,40 +196,30 @@ macro bool is_bitorder($Type) $endswitch } -macro bool is_array_or_slice_of_char(bytes) @deprecated("Use @is_array_or_slice_of_char") -{ - return @is_array_or_slice_of_char(bytes); -} - macro bool @is_array_or_slice_of_char(#bytes) @const { var $Type = $typeof(#bytes); - $switch $Type.kindof: + $switch $Type::kind: $case POINTER: - typeid $inner = $Type.inner; - return $inner.kindof == ARRAY &&& $inner.inner == char.typeid; + typeid $inner = $Type::inner; + return $inner.kind == ARRAY &&& $inner.inner == char::typeid; $case ARRAY: $case SLICE: - return $Type.inner == char.typeid; + return $Type::inner == char::typeid; $default: return false; $endswitch } -macro bool is_arrayptr_or_slice_of_char(bytes) @deprecated("Use @is_arrayptr_or_slice_of_char") -{ - return @is_arrayptr_or_slice_of_char(bytes); -} - macro bool @is_arrayptr_or_slice_of_char(#bytes) @const { var $Type = $typeof(#bytes); - $switch $Type.kindof: + $switch $Type::kind: $case POINTER: - typeid $inner = $Type.inner; - return $inner.kindof == ARRAY &&& $inner.inner == char.typeid; + typeid $inner = $Type::inner; + return $inner.kind == ARRAY &&& $inner.inner == char::typeid; $case SLICE: - return $Type.inner == char.typeid; + return $Type::inner == char::typeid; $default: return false; $endswitch diff --git a/test/stdlib/src/core/builtin.c3 b/test/stdlib/src/core/builtin.c3 index a983975..edda059 100644 --- a/test/stdlib/src/core/builtin.c3 +++ b/test/stdlib/src/core/builtin.c3 @@ -4,36 +4,6 @@ module std::core::builtin; import libc, std::hash, std::io, std::os::backtrace; - -<* - EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient - way. It relies on the fact that distinct types are not implicitly convertible. - - You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether - the argument has been used or not. - - An example: - -```c3 -macro foo(a, #b = EMPTY_MACRO_SLOT) -{ - $if @is_valid_macro_slot(#b): - return invoke_foo2(a, #b); - $else - return invoke_foo1(a); - $endif -} -*> -const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null; - -typedef EmptySlot @constinit = void*; -macro bool @is_empty_macro_slot(#arg) @const @builtin - @deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.") - => $typeof(#arg) == EmptySlot; -macro bool @is_valid_macro_slot(#arg) @const @builtin - @deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.") - => $typeof(#arg) != EmptySlot; - <* Returns a random value at compile time. @@ -61,11 +31,17 @@ faultdef TYPE_MISMATCH @builtin; Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar. */ faultdef CAPACITY_EXCEEDED @builtin; + /* Use `NOT_IMPLEMENTED` when something is conditionally available. */ faultdef NOT_IMPLEMENTED @builtin; +/* + Use `TIMEOUT` when some time or counter limit has been exceeded. +*/ +faultdef TIMEOUT @builtin; + alias VoidFn = fn void(); <* @@ -93,23 +69,23 @@ macro void @swap(#a, #b) @builtin #b = temp; } -macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u; +macro sz bitsizeof($Type) @builtin @const => $Type::size * 8u; -macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u; +macro sz @bitsizeof(#expr) @builtin @const => @sizeof(#expr) * 8u; <* Compile-time check for whether a set of constants contains a certain expression. - @param #needle : "The expression whose value should be located." + @param $needle : "The expression whose value should be located." *> -macro bool @in(#needle, ...) @builtin @const +macro bool @in($needle, ...) @builtin @const { - $for var $x = 0; $x < $vacount; $x++: - $assert $defined(#needle == $vaconst[$x]) - : "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]); - $if #needle == $vaconst[$x]: return true; $endif - $endfor - return false; + $for var $x = 0; $x < $vacount; $x++: + $assert $defined($needle == $vaconst[$x]) + : "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof($needle), $typeof($vaconst[$x]); + $if $needle == $vaconst[$x]: return true; $endif + $endfor + return false; } <* @@ -123,7 +99,7 @@ macro bool @in(#needle, ...) @builtin @const *> macro anycast(any v, $Type) @builtin { - if (v.type != $Type.typeid) return TYPE_MISMATCH~; + if (v.type != $Type::typeid) return TYPE_MISMATCH~; return ($Type*)v.ptr; } @@ -133,7 +109,7 @@ macro anycast(any v, $Type) @builtin *> macro any.to(self, $Type) { - if (self.type != $Type.typeid) return TYPE_MISMATCH~; + if (self.type != $Type::typeid) return TYPE_MISMATCH~; return *($Type*)self.ptr; } @@ -146,8 +122,6 @@ macro any.as(self, $Type) return *($Type*)self.ptr; } -macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo); - macro @addr(#val) @builtin { $if $defined(&#val): @@ -159,19 +133,82 @@ macro @addr(#val) @builtin macro typeid @typeid(#value) @const @builtin { - return $typeof(#value).typeid; + return $typeof(#value)::typeid; } -macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.") + +<* + Assert (always) that a condition is true. Otherwise call abort. + + @param #value : "The expression to test" + @param $fmt : "The formatting string" + @param ... : "Arguments to the format string" +*> +macro void always_assert(bool #value, String $fmt = "", ...) @builtin @format(1) @safemacro { - return $kindof(#value); + bool val = #value; + if (@unlikely(!val)) + { + $if $fmt == "": + abort(@sprintf("Violated assert '%s'.", $stringify(#value))); + $else + abort(@sprintf("Violated assert '%s': %s", $stringify(#value), $fmt), $vasplat); + $endif + } } -macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.") +<* + Determines whether all members of a struct/union type are trivially equatable (i.e., they all implement the `==` operator). + + @require $defined($Type::members) : "Only types with members may be used here." +*> +macro bool may_member_eq($Type) @builtin @const { - return $typeof(#value).typeid == $Type.typeid; + bool $ret = false; + $Type $a, $b; + $foreach $member : $Type::members: + // Using |= here instead of `return false;` prevents this macro from generating many runtime return lines, + // especially in the case that many of the struct fields meet this condition. + $ret = $ret ||| !$defined($member.get($a) == $member.get($b)); + $endforeach + return !$ret; } +<* + Compare two structs of the same type memberwise, using each member's `==` overload. + Importantly, this comparison _ignores_ an existing `==` overload for the struct type itself. + + @require may_member_eq($typeof(left)) : "Only structs with members that are trivially equatable with `==` are supported." + @require @kindof(left) != POINTER && @kindof(right) != POINTER : "You cannot compare structures by reference. Dereference them first." + @require $typeof(left) == $typeof(right) : "Both input structures must be the same type." +*> +macro bool member_eq(left, right) @builtin +{ + bool res = true; + $foreach $member : $typeof(left)::members: + res = res && ($member.get(left) == $member.get(right)); + $endforeach + return res; +} + +fn int panic_print_backtrace_count(BacktraceList backtrace, int backtraces_to_ignore) +{ + bool inside_builtin_panic_skipped = true; + for (int i = backtraces_to_ignore; i < backtrace.len(); i++) { + switch (backtrace[i].function) { + case "std.core.builtin.print_backtrace": + case "std.core.builtin.default_panic": + case "std.core.builtin.panicf": + if (inside_builtin_panic_skipped) backtraces_to_ignore++; + case "__GI___sigaction": + backtraces_to_ignore = i + 1; + default: + inside_builtin_panic_skipped = false; + break; + } + } + return backtraces_to_ignore; +} fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem) { @@ -185,32 +222,55 @@ fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_ba { BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces); if (catch backtrace) return false; + + // Update ignore-count to also ignore the first traces from the panic functions + backtraces_to_ignore = panic_print_backtrace_count(backtrace, backtraces_to_ignore); if (backtrace.len() <= backtraces_to_ignore) return false; + io::eprint("\nERROR: '"); io::eprint(message); io::eprintn("'"); + + sz repetition_count; foreach (i, &trace : backtrace) { if (i < backtraces_to_ignore) continue; - String inline_suffix = trace.is_inline ? " [inline]" : ""; - if (trace.is_unknown()) - { - io::eprintfn(" in ???%s", inline_suffix); - continue; + if (trace.has_file() && trace.file.ends_with("/lib/std/core/private/main_stub.c3")) { + break; + } + + // Repeating backtraces (f.e. for max recursion depth) pretty print + if (i > 0 && *trace == backtrace[i-1]) { + if (i + 1 < backtrace.len() && *trace == backtrace[i+1]) { + repetition_count += 1; + continue; + } else if (repetition_count > 0) { + if (trace.is_unknown()) { + io::eprintfn(" ... (%s repetitions of ???)", repetition_count, trace.function); + } else { + io::eprintfn(" ... (%s repetitions of %s)", repetition_count, trace.function); + } + } + } else { + repetition_count = 0; } - if (trace.has_file()) + + String inline_suffix = trace.is_inline ? " [inline]" : ""; + switch { - io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix); - continue; + case trace.is_unknown(): + io::eprintfn(" in ???%s", inline_suffix); + case trace.has_file(): + io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix); + default: + io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix); } - io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix); } }; return true; - } -fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE) +fn void default_panic(String message, String file, String function, int line) @if(env::NATIVE_STACKTRACE) { in_panic = true; $if $defined(io::stderr) && env::PANIC_MSG: @@ -231,7 +291,7 @@ macro void abort(String string = "Unrecoverable error reached", ...) @format(0) bool in_panic @private = false; -fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE) +fn void default_panic(String message, String file, String function, int line) @if (!env::NATIVE_STACKTRACE) { $if $defined(io::stderr) && env::PANIC_MSG: if (in_panic) @@ -250,11 +310,11 @@ fn void default_panic(String message, String file, String function, uint line) @ $$trap(); } -alias PanicFn = fn void(String message, String file, String function, uint line); +alias PanicFn = fn void(String message, String file, String function, int line); PanicFn panic = &default_panic; -fn void panicf(String fmt, String file, String function, uint line, args...) +fn void panicf(String fmt, String file, String function, int line, args...) { $if $defined(io::stderr) && env::PANIC_MSG: if (in_panic) @@ -299,6 +359,15 @@ macro void unsupported(String string = "Unsupported function invoked") @builtin $$unreachable(); } +<* + @require $defined(uptr[] x = { $vasplat }) : "Arguments must be convertible to uptr" +*> +macro uptr @syscall(...) @builtin +{ + return $$syscall($vasplat); +} + + <* Unconditionally break into an attached debugger when reached. *> @@ -329,16 +398,17 @@ macro any.as_inner(&self) @param expr : "the expression to cast" @param $Type : "the type to cast to" - @require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size." + @require @sizeof(expr) == $Type::size : "Cannot bitcast between types of different size." @ensure $typeof(return) == $Type* *> macro bitcast(expr, $Type) @builtin { - $if $Type.alignof <= $alignof(expr): + var $r = $reflect(expr); + $if $Type::alignment <= $r.alignment: return *($Type*)&expr; $else $Type x @noinit; - $$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr)); + $$memcpy(&x, &expr, @sizeof(expr), false, $Type::alignment, $r.alignment); return x; $endif } @@ -346,36 +416,20 @@ macro bitcast(expr, $Type) @builtin <* @param $Type : `The type of the enum` @param [in] enum_name : `The name of the enum to search for` - @require $Type.kindof == ENUM : `Only enums may be used` + @require $Type::kind == ENUM : `Only enums may be used` @ensure $typeof(return) == $Type* @return? NOT_FOUND *> macro enum_by_name($Type, String enum_name) @builtin { - typeid x = $Type.typeid; + typeid x = $Type::typeid; foreach (i, name : x.names) { - if (name == enum_name) return $Type.from_ordinal(i); + if (name == enum_name) return $Type::from_ordinal(i); } return NOT_FOUND~; } -<* - @param $Type : `The type of the enum` - @require $Type.kindof == ENUM : `Only enums may be used` - @require $defined($Type.#value) : `Expected '#value' to match an enum associated value` - @require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value` - @ensure $typeof(return) == $Type* - @return? NOT_FOUND -*> -macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup") -{ - foreach (e : $Type.values) - { - if (e.#value == value) return e; - } - return NOT_FOUND~; -} <* Mark an expression as likely to be true @@ -487,15 +541,15 @@ macro swizzle2(v, v2, ...) @builtin Returns the count of leading zero bits from an integer at compile-time. @require types::is_int($typeof($value)) : "Input value must be an integer" - @require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower" + @require @sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower" *> -macro uint @clz($value) @builtin @const +macro int @clz($value) @builtin @const { $if $value == 0: - return $sizeof($value) * 8; // it's all leading zeroes + return (int)(@sizeof($value) * 8); // it's all leading zeroes $endif - usz $n = 0; + sz $n = 0; uint128 $x = (uint128)$value; $if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif @@ -506,14 +560,14 @@ macro uint @clz($value) @builtin @const $if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif $if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif - return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type + return (int)($n % (@sizeof($value) * 8)); // mod by the bitsize of the input value to go back from uint128 -> it's-type } <* Return the excuse in the Optional if it is Empty, otherwise return a null fault. - @require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value` + @require @kindof(#expr) == OPTIONAL : `@catch expects an Optional value` *> macro fault @catch(#expr) @builtin { @@ -525,7 +579,7 @@ macro fault @catch(#expr) @builtin Check if an Optional expression holds a value or is empty, returning true if it has a value. - @require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value` + @require @kindof(#expr) == OPTIONAL : `@ok expects an Optional value` *> macro bool @ok(#expr) @builtin { @@ -599,10 +653,10 @@ macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin *> macro char[] @as_char_view(#value) @builtin { - return ((char*)&#value)[:$sizeof(#value)]; + return ((char*)&#value)[:@sizeof(#value)]; } -macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle); +macro sz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle); macro String @str_upper(String $str) @builtin => $$str_upper($str); macro String @str_lower(String $str) @builtin => $$str_lower($str); macro uint @str_hash(String $str) @builtin => $$str_hash($str); @@ -639,7 +693,7 @@ macro @generic_hash_core(h, value) macro uint @generic_hash(value) { uint h = @generic_hash_core((uint)0x3efd4391, value); - $for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4: + $for var $cnt = 4; $cnt < @sizeof(value); $cnt += 4: value >>= 32; // reduce value h = @generic_hash_core(h, value); $endfor @@ -690,11 +744,11 @@ macro uint ZString.hash(ZString c) => (uint)a5hash::hash(c.str_view()); macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr)); <* - @require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY + @require @kindof(array_ptr) == POINTER &&& @kindof(*array_ptr) == ARRAY *> macro uint hash_array(array_ptr) @local { - var $len = $sizeof(*array_ptr); + var $len = @sizeof(*array_ptr); $if $len > 16: return (uint)komi::hash(((char*)array_ptr)[:$len]); @@ -704,11 +758,11 @@ macro uint hash_array(array_ptr) @local } <* - @require $kindof(vec) == VECTOR + @require @kindof(vec) == VECTOR *> macro uint hash_vec(vec) @local { - var $len = $sizeof(vec); + var $len = @sizeof(vec); $if $len > 16: return (uint)komi::hash(((char*)&&vec)[:$len]); @@ -1002,14 +1056,14 @@ macro void* get_returnaddress(int n) fn String fault.short_name(self) { - String name = self.nameof; + String name = self.description; return name[name.index_of("::") + 2..]!!; } macro String fault.@short_name($self) @const { - var $name = $self.nameof; + var $name = $self.description; return $name[@str_find($name, "::") + 2..]; } @@ -1072,7 +1126,7 @@ char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX); // Clean this up fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE) { - $if env::LINUX: + $if env::LINUX && !env::ADDRESS_SANITIZER: Stack_t ss = { .ss_sp = &sig_stack, .ss_size = sig_stack.len diff --git a/test/stdlib/src/core/cinterop.c3 b/test/stdlib/src/core/cinterop.c3 index bc3f907..c7dfcd1 100644 --- a/test/stdlib/src/core/cinterop.c3 +++ b/test/stdlib/src/core/cinterop.c3 @@ -29,7 +29,7 @@ alias CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE)); alias CSChar = ichar; alias CUChar = char; -alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid); +alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar::typeid : char::typeid); enum CBool : char { @@ -38,26 +38,26 @@ enum CBool : char } // Helper macros -macro typeid signed_int_from_bitsize(usz $bitsize) @private +macro typeid signed_int_from_bitsize(sz $bitsize) @private { $switch $bitsize: - $case 128: return int128.typeid; - $case 64: return long.typeid; - $case 32: return int.typeid; - $case 16: return short.typeid; - $case 8: return ichar.typeid; + $case 128: return int128::typeid; + $case 64: return long::typeid; + $case 32: return int::typeid; + $case 16: return short::typeid; + $case 8: return ichar::typeid; $default: $error("Invalid bitsize"); $endswitch } -macro typeid unsigned_int_from_bitsize(usz $bitsize) @private +macro typeid unsigned_int_from_bitsize(sz $bitsize) @private { $switch $bitsize: - $case 128: return uint128.typeid; - $case 64: return ulong.typeid; - $case 32: return uint.typeid; - $case 16: return ushort.typeid; - $case 8: return char.typeid; + $case 128: return uint128::typeid; + $case 64: return ulong::typeid; + $case 32: return uint::typeid; + $case 16: return ushort::typeid; + $case 8: return char::typeid; $default: $error("Invalid bitsize"); $endswitch } @@ -68,7 +68,7 @@ module std::core::cinterop @if(USE_STACK_VALIST); typedef CVaList = void*; macro CVaList.next(&self, $Type) { - void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8)); + void *ptr = mem::aligned_pointer((void*)*self, max($Type::alignment, 8)); defer *self = (CVaList)(ptr + 1); return *($Type*)ptr; } @@ -89,29 +89,29 @@ macro CVaList.next(self, $Type) { CVaListData* data = (CVaListData*)self; $switch: - $case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16): - var $LoadType = $Type.sizeof < 8 ? double : $Type; + $case $Type::kind == FLOAT ||| ($Type::kind == VECTOR && $Type::size <= 16): + var $LoadType = $Type::size < 8 ? double : $Type; if (data.fp_offset < 6 * 8 + 8 * 16 ) { - defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16); + defer data.fp_offset += (uint)mem::aligned_offset($Type::size, 16); return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset); } - void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof)); - defer data.overflow_arg_area = ptr + $Type.sizeof; + void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type::alignment)); + defer data.overflow_arg_area = ptr + $Type::size; return ($Type)*($LoadType*)ptr; - $case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT: - var $LoadType = $Type.sizeof < 4 ? int : $Type; - if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8) + $case $Type::kind == SIGNED_INT || $Type::kind == UNSIGNED_INT: + var $LoadType = $Type::size < 4 ? int : $Type; + if (data.gp_offset < 6 * 8 && $Type::size <= 8) { - defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8); + defer data.gp_offset += (uint)mem::aligned_offset($Type::size, 8); return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset); } - void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof)); - defer data.overflow_arg_area = ptr + $Type.sizeof; + void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type::alignment)); + defer data.overflow_arg_area = ptr + $Type::size; return ($Type)*($LoadType*)ptr; $default: - void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof)); - defer data.overflow_arg_area = ptr + $Type.sizeof; + void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type::alignment)); + defer data.overflow_arg_area = ptr + $Type::size; return *($Type*)ptr; $endswitch } \ No newline at end of file diff --git a/test/stdlib/src/core/conv.c3 b/test/stdlib/src/core/conv.c3 index 7e00d46..26e03ba 100644 --- a/test/stdlib/src/core/conv.c3 +++ b/test/stdlib/src/core/conv.c3 @@ -1,5 +1,15 @@ module std::core::string::conv; +import std::bits; +enum UnicodeBom +{ + NO_BOM, + UTF8, + UTF16_LE, + UTF16_BE, + UTF32_LE, + UTF32_BE +} const uint UTF16_SURROGATE_OFFSET @private = 0x10000; const uint UTF16_SURROGATE_GENERIC_MASK @private = 0xF800; const uint UTF16_SURROGATE_GENERIC_VALUE @private = 0xD800; @@ -9,12 +19,26 @@ const uint UTF16_SURROGATE_BITS @private = 10; const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00; const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800; +fn UnicodeBom detect_bom(char[] input) +{ + if (input.len < 2) return NO_BOM; + switch (input) + { + case x'FEFF0000': return UTF32_LE; + case x'FEFF' : return UTF16_BE; + case x'FFFE' : return UTF16_LE; + case x'EFBBBF': return UTF8; + case x'0000FEFF': return UTF32_LE; + default: return NO_BOM; + } +} + <* @param c : `The utf32 codepoint to convert` @param [out] output : `the resulting buffer` @return? string::CONVERSION_FAILED *> -fn usz? char32_to_utf8(Char32 c, char[] output) +fn sz? char32_to_utf8(Char32 c, char[] output) { if (!output.len) return string::CONVERSION_FAILED~; switch (true) @@ -74,9 +98,12 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output) @param [inout] available : `amount of UTF16 data available.` @param [inout] output : `the resulting utf8 buffer to write to.` *> -fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output) +macro void? char16_to_utf8_unsafe(Char16 *ptr, sz *available, char** output, bool $unaligned, bool $byteswap) { - Char16 high = *ptr; + Char16 high = $unaligned ??? mem::load(ptr, $align:1) : *ptr; + $if $byteswap: + high = bswap(high); + $endif if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE) { char32_to_utf8_unsafe(high, output); @@ -90,7 +117,9 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output) if (*available == 1) return string::INVALID_UTF16~; Char16 low = ptr[1]; - + $if $byteswap: + low = bswap(low); + $endif // Unmatched high surrogate, invalid if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16~; @@ -105,7 +134,7 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output) @param c : `The utf32 codepoint to convert` @param [inout] output : `the resulting buffer` *> -fn usz char32_to_utf8_unsafe(Char32 c, char** output) +macro sz char32_to_utf8_unsafe(Char32 c, char** output) { switch { @@ -135,9 +164,9 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output) @param [inout] size : `Set to max characters to read, set to characters read` @return `the parsed 32 bit codepoint` *> -fn Char32? utf8_to_char32(char* ptr, usz* size) +fn Char32? utf8_to_char32(char* ptr, sz* size) { - usz max_size = *size; + sz max_size = *size; if (max_size < 1) return string::INVALID_UTF8~; char c = (ptr++)[0]; @@ -189,9 +218,9 @@ fn Char32? utf8_to_char32(char* ptr, usz* size) @param utf8 : `An UTF-8 encoded slice of bytes` @return `the number of encoded code points` *> -fn usz utf8_codepoints(String utf8) +fn sz utf8_codepoints(String utf8) { - usz len = 0; + sz len; foreach (char c : utf8) { if (c & 0xC0 != 0x80) len++; @@ -199,59 +228,109 @@ fn usz utf8_codepoints(String utf8) return len; } + <* Calculate the UTF8 length required to encode an UTF32 array. @param [in] utf32 : `the utf32 data to calculate from` + @require utf32.len % 4 == 0 : "Data must be an even 4 bytes" @return `the length of the resulting UTF8 array` *> -fn usz utf8len_for_utf32(Char32[] utf32) +fn sz utf8len_for_utf32_bytes(char[] utf32, bool swap_endianness) { - usz len = 0; - foreach (Char32 uc : utf32) + Char32[] unaligned = ((Char32*)utf32.ptr)[:utf32.len / 4]; + if (@unlikely(swap_endianness)) { - switch (true) + sz len; + foreach (&c : unaligned) { - case uc <= 0x7f: - len++; - case uc <= 0x7ff: - len += 2; - case uc <= 0xffff: - len += 3; - default: - len += 4; + Char32 uc = mem::load(c, $align:1); + len += utf8_len_for_char32(bswap(uc)); } + return len; + } + sz len; + foreach (&c : unaligned) + { + len += utf8_len_for_char32(mem::load(c, $align:1)); + } + return len; +} + +<* + Calculate the UTF8 length required to encode an UTF32 array. + @param [in] utf32 : `the utf32 data to calculate from` + @return `the length of the resulting UTF8 array` +*> +fn sz utf8len_for_utf32(Char32[] utf32) +{ + sz len; + foreach (Char32 uc : utf32) + { + len += utf8_len_for_char32(uc); } return len; } +macro sz utf8_len_for_char32(Char32 uc) @local +{ + switch + { + case uc <= 0x7f: return 1; + case uc <= 0x7ff: return 2; + case uc <= 0xffff: return 3; + default: return 4; + } +} + +macro sz utf8_len_for_char16(Char16 c) @local +{ + switch + { + case c & UTF16_SURROGATE_GENERIC_MASK == UTF16_SURROGATE_GENERIC_VALUE: return 4; + case c <= 0x7f: return 1; + case c <= 0x7ff: return 2; + default: return 3; + } +} + <* Calculate the UTF8 length required to encode an UTF16 array. @param [in] utf16 : `the utf16 data to calculate from` + @require utf16.len % 2 == 0 : "Data must be an even 2 bytes" @return `the length of the resulting UTF8 array` *> -fn usz utf8len_for_utf16(Char16[] utf16) +fn sz utf8len_for_utf16_bytes(char[] utf16, bool swap_endianness) { - usz len = 0; - usz len16 = utf16.len; - for (usz i = 0; i < len16; i++) + Char16[] unaligned = ((Char16*)utf16.ptr)[:utf16.len / 2]; + if (@unlikely(swap_endianness)) { - Char16 c = utf16[i]; - if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE) + sz len; + foreach (&c : unaligned) { - if (c <= 0x7f) - { - len++; - continue; - } - if (c <= 0x7ff) - { - len += 2; - continue; - } - len += 3; - continue; + Char16 uc = mem::load(c, $align:1); + len += utf8_len_for_char16(bswap(uc)); } - len += 4; + return len; + } + sz len; + foreach (&c : unaligned) + { + len += utf8_len_for_char16(mem::load(c, $align:1)); + } + return len; +} + +<* + Calculate the UTF8 length required to encode an UTF16 array. + @param [in] utf16 : `the utf16 data to calculate from` + @return `the length of the resulting UTF8 array` +*> +fn sz utf8len_for_utf16(Char16[] utf16) +{ + sz len; + foreach (c : utf16) + { + len += utf8_len_for_char16(c); } return len; } @@ -261,11 +340,11 @@ fn usz utf8len_for_utf16(Char16[] utf16) @param utf8 : `the utf8 data to calculate from` @return `the length of the resulting UTF16 array` *> -fn usz utf16len_for_utf8(String utf8) +fn sz utf16len_for_utf8(String utf8) { - usz len = utf8.len; - usz len16 = 0; - for (usz i = 0; i < len; i++) + sz len = utf8.len; + sz len16; + for (sz i = 0; i < len; i++) { len16++; char c = utf8[i]; @@ -284,9 +363,9 @@ fn usz utf16len_for_utf8(String utf8) @param [in] utf32 : `the UTF32 array to check the length for` @return `the required length of an UTF16 array to hold the UTF32 data.` *> -fn usz utf16len_for_utf32(Char32[] utf32) +fn sz utf16len_for_utf32(Char32[] utf32) { - usz len = utf32.len; + sz len = utf32.len; foreach (Char32 uc : utf32) { if (uc >= UTF16_SURROGATE_OFFSET) len++; @@ -301,12 +380,12 @@ fn usz utf16len_for_utf32(Char32[] utf32) @param [out] utf8_buffer @return `the number of bytes written.` *> -fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer) +fn sz? utf32to8(Char32[] utf32, char[] utf8_buffer) { char[] buffer = utf8_buffer; foreach (uc : utf32) { - usz used = char32_to_utf8(uc, buffer) @inline!; + sz used = char32_to_utf8(uc, buffer) @inline!; buffer = buffer[used..]; } // Zero terminate if there is space. @@ -314,6 +393,40 @@ fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer) return utf8_buffer.len - buffer.len; } +<* + Convert an UTF32 array to an UTF8 array. + + @param [in] utf32 + @param [out] utf8_buffer + @require utf32.len % 4 == 0 : "Length must be a multiple of 4" + @return `the number of bytes written.` +*> +fn sz? utf32_bytes_to8(char[] utf32, char[] utf8_buffer, bool swap_endianess) +{ + char[] buffer = utf8_buffer; + Char32[] unaligned = ((Char32*)utf32.ptr)[:utf32.len / 4]; + if (swap_endianess) + { + foreach (&uc : unaligned) + { + sz used = char32_to_utf8(bswap(mem::load(uc, $align:1)), buffer) @inline!; + buffer = buffer[used..]; + } + } + else + { + if (((uptr)unaligned.ptr) % 4 == 0) return utf32to8(unaligned, utf8_buffer); + foreach (&uc : unaligned) + { + sz used = char32_to_utf8(mem::load(uc, $align:1), buffer) @inline!; + buffer = buffer[used..]; + } + } + // Zero terminate if there is space. + if (buffer.len > 0) buffer[0] = 0; + return utf8_buffer.len - buffer.len; +} + <* Convert an UTF8 array to an UTF32 array. @@ -321,16 +434,16 @@ fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer) @param [out] utf32_buffer @return `the number of Char32s written.` *> -fn usz? utf8to32(String utf8, Char32[] utf32_buffer) +fn sz? utf8to32(String utf8, Char32[] utf32_buffer) { - usz len = utf8.len; + sz len = utf8.len; Char32* ptr = utf32_buffer.ptr; - usz len32 = 0; - usz buf_len = utf32_buffer.len; - for (usz i = 0; i < len;) + sz len32; + sz buf_len = utf32_buffer.len; + for (sz i = 0; i < len;) { if (len32 == buf_len) return string::CONVERSION_FAILED~; - usz width = len - i; + sz width = len - i; Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!; i += width; ptr[len32++] = uc; @@ -348,13 +461,13 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer) @param [in] utf16 : `The UTF16 array containing the data to convert.` @param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF16 data.` *> -fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer) +macro void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer, $unaligned, $byteswap) { - usz len16 = utf16.len; - for (usz i = 0; i < len16;) + sz len16 = utf16.len; + for (sz i = 0; i < len16;) { - usz available = len16 - i; - char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline!; + sz available = len16 - i; + char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer, $unaligned, $byteswap) @inline!; i += available; } } @@ -369,10 +482,10 @@ fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer) *> fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer) { - usz len = utf8.len; - for (usz i = 0; i < len;) + sz len = utf8.len; + for (sz i = 0; i < len;) { - usz width = len - i; + sz width = len - i; Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!; i += width; (utf32_buffer++)[0] = uc; @@ -389,10 +502,10 @@ fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer) *> fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer) { - usz len = utf8.len; - for (usz i = 0; i < len;) + sz len = utf8.len; + for (sz i = 0; i < len;) { - usz width = len - i; + sz width = len - i; Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!; char32_to_utf16_unsafe(uc, &utf16_buffer) @inline; i += width; @@ -406,12 +519,18 @@ fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer) @param [in] utf32 : `The UTF32 buffer containing the data to convert.` @param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF8 data.` + @param $unaligned : `If data should be loaded unaligned` + @param $byteswap : `If the result should be byteswapped` *> -fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer) +macro void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer, $unaligned, $byteswap) { char* start = utf8_buffer; - foreach (Char32 uc : utf32) + foreach (&ucref : utf32) { + Char32 uc = $unaligned ??? mem::load(ucref, $align: 1) : *ucref; + $if $byteswap: + uc = bswap(uc); + $endif char32_to_utf8_unsafe(uc, &utf8_buffer) @inline; } } diff --git a/test/stdlib/src/core/dstring.c3 b/test/stdlib/src/core/dstring.c3 index 468c430..4ac2f64 100644 --- a/test/stdlib/src/core/dstring.c3 +++ b/test/stdlib/src/core/dstring.c3 @@ -7,7 +7,7 @@ import std::io, std::math; typedef DString (OutStream) = DStringOpaque*; typedef DStringOpaque = void; -const usz MIN_CAPACITY @private = 16; +const sz MIN_CAPACITY @private = 16; <* Initialize the DString with a particular allocator. @@ -17,10 +17,10 @@ const usz MIN_CAPACITY @private = 16; @return "Return the DString itself" @require !self.data() : "String already initialized" *> -fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY) +fn DString DString.init(&self, Allocator allocator, sz capacity = MIN_CAPACITY) { if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; - StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!; + StringData* data = alloc::alloc_with_padding(allocator, StringData, capacity)!!; data.allocator = allocator; data.len = 0; data.capacity = capacity; @@ -35,21 +35,21 @@ fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY) @return "Return the DString itself" @require !self.data() : "String already initialized" *> -fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY) +fn DString DString.tinit(&self, sz capacity = MIN_CAPACITY) { return self.init(tmem, capacity) @inline; } -fn DString new_with_capacity(Allocator allocator, usz capacity) +fn DString new_with_capacity(Allocator allocator, sz capacity) { return (DString){}.init(allocator, capacity); } -fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem, capacity) @inline; +fn DString temp_with_capacity(sz capacity) => new_with_capacity(tmem, capacity) @inline; fn DString new(Allocator allocator, String c = "") { - usz len = c.len; + sz len = c.len; StringData* data = (StringData*)new_with_capacity(allocator, len); if (len) { @@ -74,9 +74,9 @@ fn void DString.replace_char(self, char ch, char replacement) fn void DString.replace(&self, String needle, String replacement) { StringData* data = self.data(); - usz needle_len = needle.len; + sz needle_len = needle.len; if (!data || data.len < needle_len) return; - usz replace_len = replacement.len; + sz replace_len = replacement.len; if (needle_len == 1 && replace_len == 1) { self.replace_char(needle[0], replacement[0]); @@ -86,8 +86,8 @@ fn void DString.replace(&self, String needle, String replacement) { String str = self.tcopy_str(); self.clear(); - usz len = str.len; - usz match = 0; + sz len = str.len; + sz match = 0; foreach (i, c : str) { if (c == needle[match]) @@ -106,7 +106,7 @@ fn void DString.replace(&self, String needle, String replacement) self.append_string(str[i - match:match]); match = 0; } - self.append_char(c); + self.append_char(c) @inline; } if (match > 0) self.append_string(str[^match:match]); }; @@ -129,7 +129,7 @@ fn ZString DString.zstr_view(&self) if (!data) return ""; if (data.capacity == data.len) { - self.reserve(1); + _reserve_inline(self, 1); data = self.data(); data.chars[data.len] = 0; } @@ -140,13 +140,13 @@ fn ZString DString.zstr_view(&self) return (ZString)&data.chars[0]; } -fn usz DString.capacity(self) +fn sz DString.capacity(self) { if (!self) return 0; return self.data().capacity; } -fn usz DString.len(&self) @dynamic @operator(len) +fn sz DString.len(&self) @dynamic @operator(len) { if (!*self) return 0; return self.data().len; @@ -155,7 +155,7 @@ fn usz DString.len(&self) @dynamic @operator(len) <* @require new_size <= self.len() *> -fn void DString.chop(self, usz new_size) +fn void DString.chop(self, sz new_size) { if (!self) return; self.data().len = new_size; @@ -163,8 +163,8 @@ fn void DString.chop(self, usz new_size) fn String DString.str_view(self) { + if (!self) return ""; StringData* data = self.data(); - if (!data) return ""; return (String)data.chars[:data.len]; } @@ -172,7 +172,7 @@ fn String DString.str_view(self) @require index < self.len() @require self.data() != null : "Empty string" *> -fn char DString.char_at(self, usz index) @operator([]) +fn char DString.char_at(self, sz index) @operator([]) { return self.data().chars[index]; } @@ -181,15 +181,15 @@ fn char DString.char_at(self, usz index) @operator([]) @require index < self.len() @require self.data() != null : "Empty string" *> -fn char* DString.char_ref(&self, usz index) @operator(&[]) +fn char* DString.char_ref(&self, sz index) @operator(&[]) { return &self.data().chars[index]; } -fn usz DString.append_utf32(&self, Char32[] chars) +fn sz DString.append_utf32(&self, Char32[] chars) { self.reserve(chars.len); - usz end = self.len(); + sz end = self.len(); foreach (Char32 c : chars) { self.append_char32(c); @@ -200,30 +200,60 @@ fn usz DString.append_utf32(&self, Char32[] chars) <* @require index < self.len() *> -fn void DString.set(self, usz index, char c) @operator([]=) +fn void DString.set(self, sz index, char c) @operator([]=) { self.data().chars[index] = c; } -fn void DString.append_repeat(&self, char c, usz times) +fn void DString.append_char_repeat(&self, char c, sz times) { if (times == 0) return; self.reserve(times); StringData* data = self.data(); - for (usz i = 0; i < times; i++) + for (sz i = 0; i < times; i++) { data.chars[data.len++] = c; } } +macro void DString.append_repeat(&self, c, sz times) +{ + if (times == 0) return; + $switch $typeof(c): + $case String: + self.append_string_repeat(c, times); + $case char: + self.append_char_repeat(c, times); + $default: + for (sz i = 0; i < times; i++) + { + self.append(c); + } + $endswitch +} + +fn void DString.append_string_repeat(&self, String c, sz times) +{ + sz strlen = c.len; + if (!times || !strlen) return; + self.reserve(times * strlen); + StringData* data = self.data(); + sz len = data.len; + for (sz i = 0; i < times; i++, len += strlen) + { + mem::copy(&data.chars[len], c, strlen); + } + data.len = len; +} + <* @require c <= 0x10ffff *> -fn usz DString.append_char32(&self, Char32 c) +fn sz DString.append_char32(&self, Char32 c) { char[4] buffer @noinit; char* p = &buffer; - usz n = conv::char32_to_utf8_unsafe(c, &p); + sz n = conv::char32_to_utf8_unsafe(c, &p); self.reserve(n); StringData* data = self.data(); data.chars[data.len:n] = buffer[:n]; @@ -238,18 +268,18 @@ fn DString DString.copy(self, Allocator allocator) @nodiscard if (!self) return new(allocator); StringData* data = self.data(); DString new_string = new_with_capacity(allocator, data.capacity); - mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len); + mem::copy((char*)new_string.data(), (char*)data, StringData::size + data.len); return new_string; } fn ZString DString.copy_zstr(self, Allocator allocator) @nodiscard { - usz str_len = self.len(); + sz str_len = self.len(); if (!str_len) { - return (ZString)allocator::calloc(allocator, 1); + return (ZString)alloc::calloc(allocator, 1); } - char* zstr = allocator::malloc(allocator, str_len + 1); + char* zstr = alloc::malloc(allocator, str_len + 1); StringData* data = self.data(); mem::copy(zstr, &data.chars, str_len); zstr[str_len] = 0; @@ -270,7 +300,7 @@ fn bool DString.equals(self, DString other_string) if (str1 == str2) return true; if (!str1) return str2.len == 0; if (!str2) return str1.len == 0; - usz str1_len = str1.len; + sz str1_len = str1.len; if (str1_len != str2.len) return false; for (int i = 0; i < str1_len; i++) { @@ -284,7 +314,7 @@ fn void DString.free(&self) if (!*self) return; StringData* data = self.data(); if (!data) return; - allocator::free(data.allocator, data); + alloc::free(data.allocator, data); *self = (DString)null; } @@ -295,8 +325,8 @@ fn bool DString.less(self, DString other_string) if (str1 == str2) return false; if (!str1) return str2.len != 0; if (!str2) return str1.len == 0; - usz str1_len = str1.len; - usz str2_len = str2.len; + sz str1_len = str1.len; + sz str2_len = str2.len; if (str1_len != str2_len) return str1_len < str2_len; for (int i = 0; i < str1_len; i++) { @@ -305,22 +335,21 @@ fn bool DString.less(self, DString other_string) return true; } -fn void DString.append_chars(&self, String str) @deprecated("Use append_string") -{ - self.append_bytes(str); -} - fn void DString.append_bytes(&self, char[] bytes) { - usz other_len = bytes.len; + sz other_len = bytes.len; if (!other_len) return; - if (!*self) + StringData* data = self.data(); + if (@unlikely(!data)) { - *self = temp((String)bytes); - return; + *self = temp_with_capacity(MIN_CAPACITY > other_len ? MIN_CAPACITY : other_len); + data = self.data(); + } + else if (@unlikely(data.len + other_len > data.capacity)) + { + _reserve_inline(self, other_len); + data = self.data(); } - self.reserve(other_len); - StringData* data = self.data(); mem::copy(&data.chars[data.len], bytes.ptr, other_len); data.len += other_len; } @@ -330,28 +359,16 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator) return self.str_view().to_utf32(allocator) @inline!!; } -<* - @require $defined(String s = str) ||| $typeof(str) == DString : "Expected string or DString" -*> -macro void DString.append_string(&self, str) -{ - $if $typeof(str) == DString: - self.append_string_deprecated(str); - $else - self.append_bytes((String)str); - $endif -} - -macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()") +fn void DString.append_string(&self, String str) { - self.append_dstring(str); + self.append_bytes((String)str) @inline; } fn void DString.append_dstring(&self, DString str) { StringData* other = str.data(); if (!other) return; - self.append(str.str_view()); + self.append_bytes(str.str_view()) @inline; } fn void DString.clear(self) @@ -360,23 +377,19 @@ fn void DString.clear(self) self.data().len = 0; } -fn usz? DString.write(&self, char[] buffer) @dynamic +fn sz? DString.write(&self, char[] buffer) @dynamic { - self.append_bytes(buffer); + self.append_bytes(buffer) @inline; return buffer.len; } fn void? DString.write_byte(&self, char c) @dynamic { - self.append_char(c); + self.append_char(c) @inline; } fn void DString.append_char(&self, char c) { - if (!*self) - { - *self = temp_with_capacity(MIN_CAPACITY); - } self.reserve(1); StringData* data = self.data(); data.chars[data.len++] = c; @@ -387,7 +400,7 @@ fn void DString.append_char(&self, char c) @require end < self.len() @require end >= start : "End must be same or equal to the start" *> -fn void DString.delete_range(&self, usz start, usz end) +fn void DString.delete_range(&self, sz start, sz end) { self.delete(start, end - start + 1); } @@ -396,17 +409,17 @@ fn void DString.delete_range(&self, usz start, usz end) @require start < self.len() @require start + len <= self.len() *> -fn void DString.delete(&self, usz start, usz len = 1) +fn void DString.delete(&self, sz start, sz len = 1) { if (!len) return; StringData* data = self.data(); - usz new_len = data.len - len; + sz new_len = data.len - len; if (new_len == 0) { data.len = 0; return; } - usz len_after = data.len - start - len; + sz len_after = data.len - start - len; if (len_after > 0) { data.chars[start:len_after] = data.chars[start + len:len_after]; @@ -439,15 +452,43 @@ macro void DString.append(&self, value) $endswitch } +<* + Same as DString.append, but may be faster due to inlining. +*> +macro void DString.append_inline(&self, value) +{ + var $Type = $typeof(value); + $switch $Type: + $case char: + $case ichar: + self.append_char(value) @inline; + $case DString: + self.append_dstring(value) @inline; + $case String: + self.append_string(value) @inline; + $case Char32: + self.append_char32(value) @inline; + $default: + $switch: + $case $defined((Char32)value): + self.append_char32((Char32)value) @inline; + $case $defined((String)value): + self.append_string((String)value) @inline; + $default: + $error "Unsupported type for append_inline – use appendf instead."; + $endswitch + $endswitch +} + <* @require index <= self.len() *> -fn void DString.insert_chars_at(&self, usz index, String s) +fn void DString.insert_chars_at(&self, sz index, String s) { if (s.len == 0) return; self.reserve(s.len); StringData* data = self.data(); - usz len = self.len(); + sz len = self.len(); if (data.chars[:len].ptr == s.ptr) { // Source and destination are the same: nothing to do. @@ -477,7 +518,7 @@ fn void DString.insert_chars_at(&self, usz index, String s) <* @require index <= self.len() *> -fn void DString.insert_string_at(&self, usz index, DString str) +fn void DString.insert_string_at(&self, sz index, DString str) { StringData* other = str.data(); if (!other) return; @@ -487,7 +528,7 @@ fn void DString.insert_string_at(&self, usz index, DString str) <* @require index <= self.len() *> -fn void DString.insert_char_at(&self, usz index, char c) +fn void DString.insert_char_at(&self, sz index, char c) { self.reserve(1); StringData* data = self.data(); @@ -501,11 +542,11 @@ fn void DString.insert_char_at(&self, usz index, char c) <* @require index <= self.len() *> -fn usz DString.insert_char32_at(&self, usz index, Char32 c) +fn sz DString.insert_char32_at(&self, sz index, Char32 c) { char[4] buffer @noinit; char* p = &buffer; - usz n = conv::char32_to_utf8_unsafe(c, &p); + sz n = conv::char32_to_utf8_unsafe(c, &p); self.reserve(n); StringData* data = self.data(); @@ -521,9 +562,9 @@ fn usz DString.insert_char32_at(&self, usz index, Char32 c) <* @require index <= self.len() *> -fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars) +fn sz DString.insert_utf32_at(&self, sz index, Char32[] chars) { - usz n = conv::utf8len_for_utf32(chars); + sz n = conv::utf8len_for_utf32(chars); self.reserve(n); StringData* data = self.data(); @@ -536,7 +577,7 @@ fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars) foreach(c : chars) { char* p = &buffer; - usz m = conv::char32_to_utf8_unsafe(c, &p); + sz m = conv::char32_to_utf8_unsafe(c, &p); data.chars[index:m] = buffer[:m]; index += m; } @@ -546,7 +587,7 @@ fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars) return n; } -macro void DString.insert_at(&self, usz index, value) +macro void DString.insert_at(&self, sz index, value) { var $Type = $typeof(value); $switch $Type: @@ -571,23 +612,22 @@ macro void DString.insert_at(&self, usz index, value) $endswitch } -import libc; -fn usz? DString.appendf(&self, String format, args...) @maydiscard +fn sz? DString.appendf(&self, String format, args...) @maydiscard { if (!self.data()) self.tinit(format.len + 20); Formatter formatter; - formatter.init(&out_string_append_fn, self); + formatter.init(&append_fn, self); return formatter.vprintf(format, args); } -fn usz? DString.appendfn(&self, String format, args...) @maydiscard +fn sz? DString.appendfn(&self, String format, args...) @maydiscard { if (!self.data()) self.tinit(format.len + 20); @pool() { Formatter formatter; - formatter.init(&out_string_append_fn, self); - usz len = formatter.vprintf(format, args)!; + formatter.init(&append_fn, self); + sz len = formatter.vprintf(format, args)!; self.append('\n'); return len + 1; }; @@ -596,7 +636,7 @@ fn usz? DString.appendfn(&self, String format, args...) @maydiscard fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard { if (!s.len) return new(allocator); - usz total_size = joiner.len * s.len; + sz total_size = joiner.len * s.len; foreach (String* &str : s) { total_size += str.len; @@ -611,72 +651,65 @@ fn DString join(Allocator allocator, String[] s, String joiner) @nodiscard return res; } -fn void? out_string_append_fn(void* data, char c) @private +fn void? append_fn(void* data, char c) { DString* s = data; - s.append_char(c); + s.append_char(c) @inline; } fn void DString.reverse(self) { StringData *data = self.data(); if (!data) return; - isz mid = data.len / 2; - for (isz i = 0; i < mid; i++) + sz mid = data.len / 2; + for (sz i = 0; i < mid; i++) { char temp = data.chars[i]; - isz reverse_index = data.len - 1 - i; + sz reverse_index = data.len - 1 - i; data.chars[i] = data.chars[reverse_index]; data.chars[reverse_index] = temp; } } -fn StringData* DString.data(self) @inline +macro StringData* DString.data(self) { return (StringData*)self; } -fn void DString.reserve(&self, usz addition) +fn void DString.reserve(&self, sz addition) @inline { - StringData* data = self.data(); - if (!data) + if (!*self) { *self = dstring::temp_with_capacity(addition); return; } - usz len = data.len + addition; - if (data.capacity >= len) return; - usz new_capacity = data.capacity * 2; - if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY; - if (new_capacity < len) new_capacity = math::next_power_of_2(len); - data.capacity = new_capacity; - *self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity); + _reserve_inline(self, addition); } -fn usz? DString.read_from_stream(&self, InStream reader) +fn sz? DString.read_from_stream(&self, InStream reader) { if (&reader.available) { - usz total_read = 0; - while (ulong available = reader.available()!) + sz total_read = 0; + while (long available = reader.available()!) { - if (available > isz.max) available = (ulong)isz.max; - self.reserve((usz)available); + if (available > sz::max) available = (ulong)sz::max; + self.reserve((sz)available); StringData* data = self.data(); - usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!; + sz len = reader.read(data.chars[data.len..(data.capacity - 1)])!; total_read += len; data.len += len; } return total_read; } - usz total_read = 0; + sz total_read = 0; while (true) { // Reserve at least 16 bytes self.reserve(16); StringData* data = self.data(); // Read into the rest of the buffer - usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!; + sz read = reader.read(data.chars[data.len..(data.capacity - 1)])!; data.len += read; // Ok, we reached the end. if (read < 16) return total_read; @@ -687,7 +720,27 @@ fn usz? DString.read_from_stream(&self, InStream reader) struct StringData @private { Allocator allocator; - usz len; - usz capacity; + sz len; + sz capacity; char[*] chars; } + +<* @require self.data() != null *> +macro _reserve_inline(DString *self, sz addition) +{ + StringData* data = self.data(); + sz len = data.len + addition; + if (@likely(data.capacity >= len)) return; + *self = (DString)_reserve_slow(data, len); +} + +<* @require data.len + addition > data.capacity *> +fn StringData* _reserve_slow(StringData* data, sz addition) @local +{ + sz len = data.len + addition; + sz new_capacity = data.capacity * 2; + if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY; + if (new_capacity < len) new_capacity = math::next_power_of_2(len); + data.capacity = new_capacity; + return alloc::realloc(data.allocator, data, StringData::size + new_capacity); +} diff --git a/test/stdlib/src/core/env.c3 b/test/stdlib/src/core/env.c3 index dd9e741..feeb783 100644 --- a/test/stdlib/src/core/env.c3 +++ b/test/stdlib/src/core/env.c3 @@ -118,16 +118,16 @@ enum ArchType const String COMPILER_BUILD_HASH = $$BUILD_HASH; const String COMPILER_BUILD_DATE = $$BUILD_DATE; -const OsType OS_TYPE = OsType.from_ordinal($$OS_TYPE); -const ArchType ARCH_TYPE = ArchType.from_ordinal($$ARCH_TYPE); -const usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE; +const OsType OS_TYPE = OsType::from_ordinal($$OS_TYPE); +const ArchType ARCH_TYPE = ArchType::from_ordinal($$ARCH_TYPE); +const sz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE; const bool ARCH_32_BIT = $$REGISTER_SIZE == 32; const bool ARCH_64_BIT = $$REGISTER_SIZE == 64; const bool LIBC = $$COMPILER_LIBC_AVAILABLE; const bool NO_LIBC = !LIBC && !CUSTOM_LIBC; const bool CUSTOM_LIBC = $$CUSTOM_LIBC; const bool OLD_IO = $feature(OLD_IO); -const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL); +const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel::from_ordinal($$COMPILER_OPT_LEVEL); const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN; const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED; const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED; @@ -136,11 +136,11 @@ const REGISTER_SIZE = $$REGISTER_SIZE; const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE; const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS; const bool BACKTRACE = $$BACKTRACE; -const usz LLVM_VERSION = $$LLVM_VERSION; +const int LLVM_VERSION = $$LLVM_VERSION; const bool BENCHMARKING = $$BENCHMARKING; const bool TESTING = $$TESTING; const bool PANIC_MSG = $$PANIC_MSG; -const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT); +const MemoryEnvironment MEMORY_ENV = MemoryEnvironment::from_ordinal($$MEMORY_ENVIRONMENT); const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING); const bool X86_64 = ARCH_TYPE == X86_64; const bool X86 = ARCH_TYPE == X86; @@ -157,7 +157,6 @@ const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD; const bool WASI = LIBC && OS_TYPE == WASI; const bool WASM = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64; const bool ANDROID = LIBC && OS_TYPE == ANDROID; -const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64; const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32; const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS; const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM; diff --git a/test/stdlib/src/core/mem.c3 b/test/stdlib/src/core/mem.c3 index 3d239b2..1661221 100644 --- a/test/stdlib/src/core/mem.c3 +++ b/test/stdlib/src/core/mem.c3 @@ -2,12 +2,11 @@ // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module std::core::mem; -import std::core::mem::allocator @public; -import std::os::posix, std::os::win32; -import std::math; +import std::core::mem::allocators @public; +import std::os::posix, std::os::win32, std::math, std::io; const MAX_MEMORY_ALIGNMENT = 0x1000_0000; -const DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (void*.alignof) * 2; +const DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (void*::alignment) * 2; const ulong KB = 1024; const ulong MB = KB * 1024; const ulong GB = MB * 1024; @@ -23,15 +22,15 @@ macro bool @constant_is_power_of_2($x) @const @private <* @return "The os page size." *> -fn usz os_pagesize() +fn sz os_pagesize() { $switch: $case env::POSIX: - static usz pagesize; + static sz pagesize; if (pagesize) return pagesize; return pagesize = posix::getpagesize(); $case env::WIN32: - static usz pagesize; + static sz pagesize; if (pagesize) return pagesize; Win32_SYSTEM_INFO info; win32::getSystemInfo(&info); @@ -48,7 +47,7 @@ fn usz os_pagesize() @param mask : "The mask for the load" @param passthru : "The value to use for non masked values" @require $defined(*ptr = passthru) : "Pointer and passthru must match" - @require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" + @require @kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require passthru.len == mask.len : "Mask and passthru must have the same length" @return "A vector with the loaded values where the mask is true, passthru where the mask is false" @@ -67,14 +66,14 @@ macro masked_load(ptr, bool[<*>] mask, passthru) @param $alignment : "The alignment to assume for the pointer" @require $defined(*ptr = passthru) : "Pointer and passthru must match" - @require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" + @require @kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require passthru.len == mask.len : "Mask and passthru must have the same length" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two" @return "A vector with the loaded values where the mask is true, passthru where the mask is false" @ensure $typeof(return) == $typeof(*ptr) *> -macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment) +macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, sz $alignment) { return $$masked_load(ptr, mask, passthru, $alignment); } @@ -86,8 +85,8 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment) @param mask : "The mask for the load" @param passthru : "The value to use for non masked values" - @require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" - @require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" + @require @kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" + @require @kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match" @require passthru.len == mask.len : "Mask and passthru must have the same length" @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length" @@ -108,8 +107,8 @@ macro gather(ptrvec, bool[<*>] mask, passthru) @param passthru : "The value to use for non masked values" @param $alignment : "The alignment to assume for the pointers" - @require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" - @require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" + @require @kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" + @require @kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match" @require passthru.len == mask.len : "Mask and passthru must have the same length" @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length" @@ -117,7 +116,7 @@ macro gather(ptrvec, bool[<*>] mask, passthru) @return "A vector with the loaded values where the mask is true, passthru where the mask is false" *> -macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment) +macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, sz $alignment) { return $$gather(ptrvec, mask, passthru, $alignment); } @@ -131,7 +130,7 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment) @param mask : "The mask for the store" @require $defined(*ptr = value) : "Pointer and value must match" - @require $kindof(value) == VECTOR : "Expected value to be a vector" + @require @kindof(value) == VECTOR : "Expected value to be a vector" @require value.len == mask.len : "Mask and value must have the same length" *> macro masked_store(ptr, value, bool[<*>] mask) @@ -146,12 +145,12 @@ macro masked_store(ptr, value, bool[<*>] mask) @param $alignment : "The alignment of the pointer" @require $defined(*ptr = value) : "Pointer and value must match" - @require $kindof(value) == VECTOR : "Expected value to be a vector" + @require @kindof(value) == VECTOR : "Expected value to be a vector" @require value.len == mask.len : "Mask and value must have the same length" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two" *> -macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment) +macro @masked_store_aligned(ptr, value, bool[<*>] mask, sz $alignment) { return $$masked_store(ptr, value, mask, $alignment); } @@ -160,8 +159,8 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment) @param ptrvec : "The vector pointer containing the addresses to store to." @param value : "The value to store masked" @param mask : "The mask for the store" - @require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" - @require $kindof(value) == VECTOR : "Expected value to be a vector" + @require @kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" + @require @kindof(value) == VECTOR : "Expected value to be a vector" @require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match" @require value.len == mask.len : "Mask and value must have the same length" @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length" @@ -178,45 +177,18 @@ macro scatter(ptrvec, value, bool[<*>] mask) @param mask : "The mask for the store" @param $alignment : "The alignment of the load" - @require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" - @require $kindof(value) == VECTOR : "Expected value to be a vector" + @require @kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" + @require @kindof(value) == VECTOR : "Expected value to be a vector" @require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match" @require value.len == mask.len : "Mask and value must have the same length" @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two" *> -macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment) +macro @scatter_aligned(ptrvec, value, bool[<*>] mask, sz $alignment) { return $$scatter(ptrvec, value, mask, $alignment); } -<* - @param #x : "The variable or dereferenced pointer to load." - @param $alignment : "The alignment to assume for the load" - @return "The value of the variable" - - @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two" - @require $defined(&#x) : "This must be a variable or dereferenced pointer" -*> -macro @unaligned_load(#x, usz $alignment) @builtin -{ - return $$unaligned_load(&#x, $alignment, false); -} - -<* - @param #x : "The variable or dereferenced pointer to store to." - @param value : "The value to store." - @param $alignment : "The alignment to assume for the store" - @return "The value stored" - - @require $defined(&#x) : "This must be a variable or dereferenced pointer" - @require $defined(#x = value) : "The value doesn't match the variable" - @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two" -*> -macro @unaligned_store(#x, value, usz $alignment) @builtin -{ - return $$unaligned_store(&#x, ($typeof(#x))value, $alignment, false); -} <* @@ -252,7 +224,7 @@ macro @volatile_store(#x, value) @builtin @require $defined(*ptr) : "This must be a typed pointer" @require @constant_is_power_of_2($align) : "The alignment must be a power of two" *> -macro load(ptr, usz $align, bool $volatile = false) +macro load(ptr, sz $align, bool $volatile = false) { return $$unaligned_load(ptr, $align, $volatile); } @@ -268,7 +240,7 @@ macro load(ptr, usz $align, bool $volatile = false) @require $defined(*ptr = value) : "The value doesn't match the variable" @require @constant_is_power_of_2($align) : "The alignment must be a power of two" *> -macro store(ptr, value, usz $align, bool $volatile = false) +macro store(ptr, value, sz $align, bool $volatile = false) { return $$unaligned_store(ptr, ($typeof(*ptr))value, $align, $volatile); } @@ -324,7 +296,7 @@ macro void @atomic_store(#x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $ @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid." @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid." *> -macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0) +macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, sz $alignment = 0) { return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment); } @@ -341,40 +313,40 @@ macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = S <* @require math::is_power_of_2(alignment) *> -fn usz aligned_offset(usz offset, usz alignment) +fn sz aligned_offset(sz offset, sz alignment) { return (offset + alignment - 1) & ~(alignment - 1); } -macro void* aligned_pointer(void* ptr, usz alignment) +macro void* aligned_pointer(void* ptr, sz alignment) { - return (void*)(uptr)aligned_offset((uptr)ptr, alignment); + return (void*)(((uptr)ptr + (uptr)alignment - 1u) & ~((uptr)alignment - 1u)); } <* @require math::is_power_of_2(alignment) *> -fn bool ptr_is_aligned(void* ptr, usz alignment) @inline +fn bool ptr_is_aligned(void* ptr, sz alignment) @inline { - return (uptr)ptr & ((uptr)alignment - 1) == 0; + return (uptr)ptr & ((uptr)alignment - 1u) == 0; } fn bool ptr_is_page_aligned(void* ptr) @inline { - return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0; + return (uptr)ptr & ((uptr)os_pagesize() - 1u) == 0; } macro void zero_volatile(char[] data) { - $$memset(data.ptr, (char)0, data.len, true, (usz)1); + $$memset(data.ptr, (char)0, data.len, true, (sz)1); } -macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false) +macro void clear(void* dst, sz len, sz $dst_align = 0, bool $is_volatile = false) { $$memset(dst, (char)0, len, $is_volatile, $dst_align); } -macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false) +macro void clear_inline(void* dst, sz $len, sz $dst_align = 0, bool $is_volatile = false) { $$memset_inline(dst, (char)0, $len, $is_volatile, $dst_align); } @@ -392,7 +364,7 @@ macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volati @require src != null || len == 0 : "Copying a null with non-zero length is invalid" @require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap" *> -macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) +macro void copy(void* dst, void* src, sz len, sz $dst_align = 0, sz $src_align = 0, bool $is_volatile = false) { $$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align); } @@ -411,7 +383,7 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig @require src != null || $len == 0 : "Copying a null with non-zero length is invalid" @require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap" *> -macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) +macro void copy_inline(void* dst, void* src, sz $len, sz $dst_align = 0, sz $src_align = 0, bool $is_volatile = false) { $$memcpy_inline(dst, src, $len, $is_volatile, $dst_align, $src_align); } @@ -428,7 +400,7 @@ macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $ @require src != null || len == 0 : "Moving a null with non-zero length is invalid" *> -macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) +macro void move(void* dst, void* src, sz len, sz $dst_align = 0, sz $src_align = 0, bool $is_volatile = false) { $$memmove(dst, src, len, $is_volatile, $dst_align, $src_align); } @@ -444,7 +416,7 @@ macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig @ensure !len || (dst[0] == val && dst[len - 1] == val) *> -macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false) +macro void set(void* dst, char val, sz len, sz $dst_align = 0, bool $is_volatile = false) { $$memset(dst, val, len, $is_volatile, $dst_align); } @@ -460,7 +432,7 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati @ensure !$len || (dst[0] == val && dst[$len - 1] == val) *> -macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false) +macro void set_inline(void* dst, char val, sz $len, sz $dst_align = 0, bool $is_volatile = false) { $$memset_inline(dst, val, $len, $is_volatile, $dst_align); } @@ -473,10 +445,10 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i @require values::@inner_kind(a) != TypeKind.POINTER || len > -1 @require $defined(a = b, b = a) *> -macro bool equals(a, b, isz len = -1, usz $align = 0) +macro bool equals(a, b, sz len = -1, sz $align = 0) { $if !$align: - $align = $typeof(a[0]).alignof; + $align = $typeof(a[0])::alignment; $endif void* x @noinit; void* y @noinit; @@ -504,14 +476,14 @@ macro bool equals(a, b, isz len = -1, usz $align = 0) $default: $Type = ulong; $endswitch - var $step = $Type.sizeof; - usz end = len / $step; - for (usz i = 0; i < end; i++) + var $step = $Type::size; + sz end = len / $step; + for (sz i = 0; i < end; i++) { if ((($Type*)x)[i] != (($Type*)y)[i]) return false; } - usz last = len % $align; - for (usz i = len - last; i < len; i++) + sz last = len % $align; + for (sz i = len - last; i < len; i++) { if (((char*)x)[i] != ((char*)y)[i]) return false; } @@ -525,7 +497,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0) *> macro bool type_alloc_must_be_aligned($Type) { - return $Type.alignof > DEFAULT_MEM_ALIGNMENT; + return $Type::alignment > DEFAULT_MEM_ALIGNMENT; } <* @@ -533,9 +505,9 @@ macro bool type_alloc_must_be_aligned($Type) *> macro void @scoped(Allocator allocator; @body()) { - Allocator old_allocator = allocator::thread_allocator; - allocator::thread_allocator = allocator; - defer allocator::thread_allocator = old_allocator; + Allocator old_allocator = allocators::thread_allocator; + allocators::thread_allocator = allocator; + defer allocators::thread_allocator = old_allocator; @body(); } @@ -549,12 +521,12 @@ macro void @report_heap_allocs_in_scope($enabled = true; @body()) { $if $enabled: TrackingAllocator tracker; - tracker.init(allocator::thread_allocator); - Allocator old_allocator = allocator::thread_allocator; - allocator::thread_allocator = &tracker; + tracker.init(allocators::thread_allocator); + Allocator old_allocator = allocators::thread_allocator; + allocators::thread_allocator = &tracker; defer { - allocator::thread_allocator = old_allocator; + allocators::thread_allocator = old_allocator; tracker.print_report(); tracker.free(); } @@ -565,32 +537,30 @@ macro void @report_heap_allocs_in_scope($enabled = true; @body()) <* Assert on memory leak in the scope of the macro body. + Typically this is disabled on --safe=no, but it can be enabled even when safety + is off by adding the MEMORY_ASSERTS flag through "-D MEMORY_ASSERTS" + @param $report : "Set to false to disable memory report" *> macro void @assert_leak($report = true; @body()) @builtin { - $if env::DEBUG_SYMBOLS || $feature(MEMORY_ASSERTS): + $if env::COMPILER_SAFE_MODE || $feature(MEMORY_ASSERTS): TrackingAllocator tracker; tracker.init(mem); Allocator old_allocator = mem; - allocator::thread_allocator = &tracker; + allocators::thread_allocator = &tracker; defer { - allocator::thread_allocator = old_allocator; + allocators::thread_allocator = old_allocator; defer tracker.free(); - usz allocated = tracker.allocated(); + sz allocated = tracker.allocated(); if (allocated) { - DString report; - report.init(old_allocator); - defer report.free(); + io::eprintfn("Memory leak detected (%d bytes allocated).", allocated); $if $report: - report.append_char('\n'); - (void)tracker.fprint_report(&report); + (void)tracker.fprint_report(io::stderr()); $endif - assert(allocated == 0, "Memory leak detected" - " (%d bytes allocated).%s", - allocated, report.str_view()); + abort("Aborting due to memory leak."); } } $endif @@ -605,7 +575,7 @@ macro void @assert_leak($report = true; @body()) @builtin @param $size : `the size of the buffer` *> -macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin +macro void @stack_mem(sz $size; @body(Allocator mem)) @builtin { char[$size] buffer; OnStackAllocator allocator; @@ -614,7 +584,7 @@ macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin @body(&allocator); } -macro void @stack_pool(usz $size; @body) @builtin +macro void @stack_pool(sz $size; @body) @builtin { char[$size] buffer; OnStackAllocator allocator; @@ -631,7 +601,7 @@ macro void @stack_pool(usz $size; @body) @builtin *> fn PoolState temp_push() { - return allocator::push_pool() @inline; + return allocators::push_pool() @inline; } <* @@ -639,28 +609,28 @@ fn PoolState temp_push() *> fn void temp_pop(PoolState old_state) { - allocator::pop_pool(old_state) @inline; + allocators::pop_pool(old_state) @inline; } <* @require pool_size >= 64 @require realloc_size >= 64 - @require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator." - @require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes" + @require allocator.type != TempAllocator::typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator." + @require min_size > TempAllocator::size + 64 : "Min size must meaningfully hold the data + some bytes" *> -macro void @pool_init(Allocator allocator, usz pool_size, - usz reserve_size = allocator::temp_allocator_reserve_size, - usz min_size = allocator::temp_allocator_min_size, - usz realloc_size = allocator::temp_allocator_realloc_size; @body) @builtin -{ - Allocator current = allocator::current_temp; - TempAllocator* top = allocator::top_temp; - allocator::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size); +macro void @pool_init(Allocator allocator, sz pool_size, + sz reserve_size = allocators::temp_allocator_reserve_size, + sz min_size = allocators::temp_allocator_min_size, + sz realloc_size = allocators::temp_allocator_realloc_size; @body) @builtin +{ + Allocator current = allocators::current_temp; + TempAllocator* top = allocators::top_temp; + allocators::create_temp_allocator(allocator, pool_size, reserve_size, min_size, realloc_size); defer { - allocator::destroy_temp_allocators(); - allocator::top_temp = top; - allocator::current_temp = current; + allocators::destroy_temp_allocators(); + allocators::top_temp = top; + allocators::current_temp = current; } @body(); } @@ -674,30 +644,30 @@ macro void @pool_init(Allocator allocator, usz pool_size, @param reserve : "The amount of bytes to reserve for out-of-order allocations, 0 gives the default." *> -macro void @pool(usz reserve = 0; @body) @builtin +macro void @pool(sz reserve = 0; @body) @builtin { - PoolState state = allocator::push_pool(reserve) @inline; + PoolState state = allocators::push_pool(reserve) @inline; defer { - allocator::pop_pool(state) @inline; + allocators::pop_pool(state) @inline; } @body(); } module std::core::mem @if(env::FREESTANDING_WASM); -import std::core::mem::allocator @public; +import std::core::mem::allocators @public; SimpleHeapAllocator wasm_allocator @private; extern int __heap_base; fn void initialize_wasm_mem() @init(1024) @private { - allocator::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null. + allocators::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null. // Check if we need to move the heap. - uptr start = (uptr)&__heap_base; - if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start; - wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x)); - allocator::thread_allocator = &wasm_allocator; - allocator::temp_base_allocator = &wasm_allocator; + iptr start = (iptr)&__heap_base; + if (start > mem::DEFAULT_MEM_ALIGNMENT) allocators::wasm_memory.use = start; + wasm_allocator.init(fn (x) => allocators::wasm_memory.allocate_block(x)); + allocators::thread_allocator = &wasm_allocator; + allocators::temp_base_allocator = &wasm_allocator; } module std::core::mem; @@ -715,18 +685,18 @@ macro TrackingEnv* get_tracking_env() <* @param value : "The value to clone" @return "A pointer to the cloned value" - @require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead" + @require @alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead" *> macro @clone(value) @builtin @nodiscard { - return allocator::clone(mem, value); + return alloc::clone(mem, value); } <* @param value : "The value to clone" @return "A pointer to the cloned value" *> -macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value); +macro @clone_slice(value) @builtin @nodiscard => alloc::clone_slice(mem, value); <* @param value : "The value to clone" @@ -734,7 +704,7 @@ macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, val *> macro @clone_aligned(value) @builtin @nodiscard { - return allocator::clone_aligned(mem, value); + return alloc::clone_aligned(mem, value); } <* @@ -743,10 +713,10 @@ macro @clone_aligned(value) @builtin @nodiscard *> macro @tclone(value) @builtin @nodiscard { - $if $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT: + $if @alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT: return tnew($typeof(value), value); $else - return allocator::clone_aligned(tmem, value); + return alloc::clone_aligned(tmem, value); $endif } @@ -754,23 +724,23 @@ macro @tclone(value) @builtin @nodiscard @param value : "The value to clone" @return "A pointer to the cloned value" *> -macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value); +macro @tclone_slice(value) @builtin @nodiscard => alloc::clone_slice(tmem, value); -fn void* malloc(usz size) @builtin @inline @nodiscard +fn void* malloc(sz size) @builtin @inline @nodiscard { - return allocator::malloc(mem, size); + return alloc::malloc(mem, size); } <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. *> -fn void* malloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard +fn void* malloc_aligned(sz size, sz alignment) @builtin @inline @nodiscard { - return allocator::malloc_aligned(mem, size, alignment)!!; + return alloc::malloc_aligned(mem, size, alignment)!!; } -fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard +fn void* tmalloc(sz size, sz alignment = 0) @builtin @inline @nodiscard { if (!size) return null; return tmem.acquire(size, NO_ZERO, alignment)!!; @@ -781,17 +751,17 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard @param #init : "The optional initializer" @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" + @require $Type::alignment <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to data of type $Type." *> macro new($Type, #init = ...) @nodiscard @safemacro { $if $defined(#init): - $Type* val = malloc($Type.sizeof); + $Type* val = malloc($Type::size); *val = #init; return val; $else - return ($Type*)calloc($Type.sizeof); + return ($Type*)calloc($Type::size); $endif } @@ -803,14 +773,14 @@ macro new($Type, #init = ...) @nodiscard @safemacro @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to data of type $Type." *> -macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro +macro new_with_padding($Type, sz padding, #init = ...) @nodiscard @safemacro { $if $defined(#init): - $Type* val = malloc($Type.sizeof + padding); + $Type* val = malloc($Type::size + padding); *val = #init; return val; $else - return ($Type*)calloc($Type.sizeof + padding); + return ($Type*)calloc($Type::size + padding); $endif } @@ -827,37 +797,37 @@ macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro macro new_aligned($Type, #init = ...) @nodiscard @safemacro { $if $defined(#init): - $Type* val = malloc_aligned($Type.sizeof, $Type.alignof); + $Type* val = malloc_aligned($Type::size, $Type::alignment); *val = #init; return val; $else - return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof); + return ($Type*)calloc_aligned($Type::size, $Type::alignment); $endif } <* @param $Type : "The type to allocate" - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require $Type::alignment <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to uninitialized data for the type $Type" *> macro alloc($Type) @nodiscard { - return ($Type*)malloc($Type.sizeof); + return ($Type*)malloc($Type::size); } <* @param $Type : "The type to allocate" @param padding : "The padding to add after the allocation" - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require $Type::alignment <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to uninitialized data for the type $Type" *> -macro alloc_with_padding($Type, usz padding) @nodiscard +macro alloc_with_padding($Type, sz padding) @nodiscard { - return ($Type*)malloc($Type.sizeof + padding); + return ($Type*)malloc($Type::size + padding); } <* @@ -866,30 +836,30 @@ macro alloc_with_padding($Type, usz padding) @nodiscard exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. @param $Type : "The type to allocate" - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" @return "A pointer to uninitialized data for the type $Type with the proper alignment" *> macro alloc_aligned($Type) @nodiscard { - return ($Type*)malloc_aligned($Type.sizeof, $Type.alignof); + return ($Type*)malloc_aligned($Type::size, $Type::alignment); } <* @param $Type : "The type to allocate" @param #init : "The optional initializer" - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type." *> macro tnew($Type, #init = ...) @nodiscard @safemacro { $if $defined(#init): - $Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline; + $Type* val = tmalloc($Type::size, $Type::alignment) @inline; *val = #init; return val; $else - return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline; + return ($Type*)tcalloc($Type::size, $Type::alignment) @inline; $endif } @@ -898,128 +868,138 @@ macro tnew($Type, #init = ...) @nodiscard @safemacro @param padding : "The padding to add after the allocation" @param #init : "The optional initializer" - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type with added padding at the end." *> -macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro +macro temp_with_padding($Type, sz padding, #init = ...) @nodiscard @safemacro { $if $defined(#init): - $Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline; + $Type* val = tmalloc($Type::size + padding, $Type::alignment) @inline; *val = #init; return val; $else - return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline; + return ($Type*)tcalloc($Type::size + padding, $Type::alignment) @inline; $endif } <* - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" *> macro talloc($Type) @nodiscard { - return tmalloc($Type.sizeof, $Type.alignof); + return tmalloc($Type::size, $Type::alignment); } <* - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" *> -macro talloc_with_padding($Type, usz padding) @nodiscard +macro talloc_with_padding($Type, sz padding) @nodiscard { - return tmalloc($Type.sizeof + padding, $Type.alignof); + return tmalloc($Type::size + padding, $Type::alignment); } <* - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require $Type::alignment <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" *> -macro new_array($Type, usz elements) @nodiscard +macro new_array($Type, sz elements) @nodiscard { - return allocator::new_array(mem, $Type, elements); + return alloc::new_array(mem, $Type, elements); } <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" *> -macro new_array_aligned($Type, usz elements) @nodiscard +macro new_array_aligned($Type, sz elements) @nodiscard { - return allocator::new_array_aligned(mem, $Type, elements); + return alloc::new_array_aligned(mem, $Type, elements); } <* - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require $Type::alignment <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" *> -macro alloc_array($Type, usz elements) @nodiscard +macro alloc_array($Type, sz elements) @nodiscard { - return allocator::alloc_array(mem, $Type, elements); + return alloc::alloc_array(mem, $Type, elements); } <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. - @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type::kind != OPTIONAL : "Expected a non-optional type" + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" *> -macro alloc_array_aligned($Type, usz elements) @nodiscard +macro alloc_array_aligned($Type, sz elements) @nodiscard { - return allocator::alloc_array_aligned(mem, $Type, elements); + return alloc::alloc_array_aligned(mem, $Type, elements); } -macro talloc_array($Type, usz elements) @nodiscard +<* + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro talloc_array($Type, sz elements) @nodiscard { - return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements]; + return (($Type*)tmalloc($Type::size * elements, $Type::alignment))[:elements]; } -macro temp_array($Type, usz elements) @nodiscard +<* + @require !@catch($Type::size.overflow_mul(elements)) : "Resulting array size would overflow sz::max" +*> +macro temp_array($Type, sz elements) @nodiscard { - return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements]; + return (($Type*)tcalloc($Type::size * elements, $Type::alignment))[:elements]; } -fn void* calloc(usz size) @builtin @inline @nodiscard +fn void* calloc(sz size) @builtin @inline @nodiscard { - return allocator::calloc(mem, size); + return alloc::calloc(mem, size); } <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. *> -fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard +fn void* calloc_aligned(sz size, sz alignment) @builtin @inline @nodiscard { - return allocator::calloc_aligned(mem, size, alignment)!!; + return alloc::calloc_aligned(mem, size, alignment)!!; } -fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard +fn void* tcalloc(sz size, sz alignment = 0) @builtin @inline @nodiscard { if (!size) return null; return tmem.acquire(size, ZERO, alignment)!!; } -fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard +fn void* realloc(void *ptr, sz new_size) @builtin @inline @nodiscard { - return allocator::realloc(mem, ptr, new_size); + return alloc::realloc(mem, ptr, new_size); } -fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard +fn void* realloc_aligned(void *ptr, sz new_size, sz alignment) @builtin @inline @nodiscard { - return allocator::realloc_aligned(mem, ptr, new_size, alignment)!!; + return alloc::realloc_aligned(mem, ptr, new_size, alignment)!!; } fn void free(void* ptr) @builtin @inline { - return allocator::free(mem, ptr); + return alloc::free(mem, ptr); } fn void free_aligned(void* ptr) @builtin @inline { - return allocator::free_aligned(mem, ptr); + return alloc::free_aligned(mem, ptr); } -fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard +fn void* trealloc(void* ptr, sz size, sz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard { if (!size) return null; if (!ptr) return tmalloc(size, alignment); @@ -1035,16 +1015,16 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN *> macro @unaligned_addr(#arg) @builtin { - return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg; + return (UnalignedRef{$typeof(#arg), @alignof(#arg)})&#arg; } module std::core::mem @if(env::NO_LIBC); -fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp") +fn CInt __memcmp(void* s1, void* s2, sz n) @weak @export("memcmp") { char* p1 = s1; char* p2 = s2; - for (usz i = 0; i < n; i++, p1++, p2++) + for (sz i = 0; i < n; i++, p1++, p2++) { char c1 = *p1; char c2 = *p2; @@ -1054,22 +1034,22 @@ fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp") return 0; } -fn void* __memset(void* str, CInt c, usz n) @weak @export("memset") +fn void* __memset(void* str, CInt c, sz n) @weak @export("memset") { char* p = str; char cc = (char)c; - for (usz i = 0; i < n; i++, p++) + for (sz i = 0; i < n; i++, p++) { *p = cc; } return str; } -fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy") +fn void* __memcpy(void* dst, void* src, sz n) @weak @export("memcpy") { char* d = dst; char* s = src; - for (usz i = 0; i < n; i++, d++, s++) + for (sz i = 0; i < n; i++, d++, s++) { *d = *s; } @@ -1079,7 +1059,7 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy") module std::core::mem::volatile ; -typedef Volatile @structlike = Type; +typedef Volatile = Type; macro Type Volatile.get(&self) { @@ -1104,10 +1084,10 @@ typedef UnalignedRef = Type*; macro Type UnalignedRef.get(self) { - return @unaligned_load(*(Type*)self, ALIGNMENT, false); + return mem::load((Type*)self, ALIGNMENT); } macro Type UnalignedRef.set(&self, Type val) { - return @unaligned_store(*(Type*)self, val, ALIGNMENT, false); + return mem::store((Type*)self, val, ALIGNMENT); } diff --git a/test/stdlib/src/core/mem_allocator.c3 b/test/stdlib/src/core/mem_allocator.c3 deleted file mode 100644 index abf7d84..0000000 --- a/test/stdlib/src/core/mem_allocator.c3 +++ /dev/null @@ -1,646 +0,0 @@ -module std::core::mem::allocator; -import std::math; - -// C3 has several different allocators available: -// -// Name Arena Uses buffer OOM Fallback? Mark? Reset? -// ArenaAllocator Yes Yes No Yes Yes -// BackedArenaAllocator Yes No Yes Yes Yes -// DynamicArenaAllocator Yes No Yes No Yes -// HeapAllocator No No No No No *Note: Not for normal use -// LibcAllocator No No No No No *Note: Wraps malloc -// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem -// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool -// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator -// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes - -const DEFAULT_SIZE_PREFIX = usz.sizeof; -const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof; - -struct TrackingEnv -{ - String file; - String function; - uint line; -} - -enum AllocInitType -{ - NO_ZERO, - ZERO -} - -interface Allocator -{ - <* - Acquire memory from the allocator, with the given alignment and initialization type. - - @require !alignment || math::is_power_of_2(alignment) - @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` - @require size > 0 : "The size must be 1 or more" - @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY - *> - fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0); - - <* - Resize acquired memory from the allocator, with the given new size and alignment. - - @require !alignment || math::is_power_of_2(alignment) - @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` - @require ptr != null - @require new_size > 0 - @return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY - *> - fn void*? resize(void* ptr, usz new_size, usz alignment = 0); - - <* - Release memory acquired using `acquire` or `resize`. - - @require ptr != null : "Empty pointers should never be released" - *> - fn void release(void* ptr, bool aligned); -} - -alias MemoryAllocFn = fn char[]?(usz); - - - -fn usz alignment_for_allocation(usz alignment) @inline -{ - return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment; -} - -macro void* malloc(Allocator allocator, usz size) @nodiscard -{ - return malloc_try(allocator, size)!!; -} - -macro void*? malloc_try(Allocator allocator, usz size) @nodiscard -{ - if (!size) return null; - $if env::TESTING: - char* data = allocator.acquire(size, NO_ZERO)!; - mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); - return data; - $else - return allocator.acquire(size, NO_ZERO); - $endif -} - -macro void* calloc(Allocator allocator, usz size) @nodiscard -{ - return calloc_try(allocator, size)!!; -} - -macro void*? calloc_try(Allocator allocator, usz size) @nodiscard -{ - if (!size) return null; - return allocator.acquire(size, ZERO); -} - -macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard -{ - return realloc_try(allocator, ptr, new_size)!!; -} - -macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard -{ - if (!new_size) - { - free(allocator, ptr); - return null; - } - if (!ptr) return allocator.acquire(new_size, NO_ZERO); - return allocator.resize(ptr, new_size); -} - -macro void free(Allocator allocator, void* ptr) -{ - if (!ptr) return; - $if env::TESTING: - ((char*)ptr)[0] = 0xBA; - $endif - allocator.release(ptr, false); -} - -macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard -{ - if (!size) return null; - $if env::TESTING: - char* data = allocator.acquire(size, NO_ZERO, alignment)!; - mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT); - return data; - $else - return allocator.acquire(size, NO_ZERO, alignment); - $endif -} - -macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard -{ - if (!size) return null; - return allocator.acquire(size, ZERO, alignment); -} - -macro void*? realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard -{ - if (!new_size) - { - free_aligned(allocator, ptr); - return null; - } - if (!ptr) - { - return malloc_aligned(allocator, new_size, alignment); - } - return allocator.resize(ptr, new_size, alignment); -} - -macro void free_aligned(Allocator allocator, void* ptr) -{ - if (!ptr) return; - $if env::TESTING: - ((char*)ptr)[0] = 0xBA; - $endif - allocator.release(ptr, aligned: true); -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type" -*> -macro new(Allocator allocator, $Type, ...) @nodiscard -{ - $if $vacount == 0: - return ($Type*)calloc(allocator, $Type.sizeof); - $else - $Type* val = malloc(allocator, $Type.sizeof); - *val = $vaexpr[0]; - return val; - $endif -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type" -*> -macro new_try(Allocator allocator, $Type, ...) @nodiscard -{ - $if $vacount == 0: - return ($Type*)calloc_try(allocator, $Type.sizeof); - $else - $Type* val = malloc_try(allocator, $Type.sizeof)!; - *val = $vaexpr[0]; - return val; - $endif -} - -<* - Allocate using an aligned allocation. This is necessary for types with a default memory alignment - exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type" -*> -macro new_aligned(Allocator allocator, $Type, ...) @nodiscard -{ - $if $vacount == 0: - return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof); - $else - $Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!; - *val = $vaexpr[0]; - return val; - $endif -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT -*> -macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard -{ - return ($Type*)calloc_try(allocator, $Type.sizeof + padding); -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" -*> -macro alloc(Allocator allocator, $Type) @nodiscard -{ - return ($Type*)malloc(allocator, $Type.sizeof); -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" -*> -macro alloc_try(Allocator allocator, $Type) @nodiscard -{ - return ($Type*)malloc_try(allocator, $Type.sizeof); -} - -<* - Allocate using an aligned allocation. This is necessary for types with a default memory alignment - exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. -*> -macro alloc_aligned(Allocator allocator, $Type) @nodiscard -{ - return ($Type*)malloc_aligned(allocator, $Type.sizeof, $Type.alignof); -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT -*> -macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard -{ - return ($Type*)malloc_try(allocator, $Type.sizeof + padding); -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" -*> -macro new_array(Allocator allocator, $Type, usz elements) @nodiscard -{ - return new_array_try(allocator, $Type, elements)!!; -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" -*> -macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard -{ - return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements]; -} - -<* - Allocate using an aligned allocation. This is necessary for types with a default memory alignment - exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. -*> -macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard -{ - return (($Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!; -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" -*> -macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard -{ - return alloc_array_try(allocator, $Type, elements)!!; -} - -<* - Allocate using an aligned allocation. This is necessary for types with a default memory alignment - exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. -*> -macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard -{ - return (($Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!; -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" -*> -macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard -{ - return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements]; -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" -*> -macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard -{ - return realloc_array_try(allocator, ptr, $Type, elements)!!; -} - -<* - Allocate using an aligned allocation. This is necessary for types with a default memory alignment - exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. -*> -macro realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard -{ - return (($Type*)realloc_aligned(allocator, ptr, $Type.sizeof * elements, $Type.alignof))[:elements]!!; -} - -<* - @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" -*> -macro realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard -{ - return (($Type*)realloc_try(allocator, ptr, $Type.sizeof * elements))[:elements]; -} - -<* - Clone a value. - - @param [&inout] allocator : "The allocator to use to clone" - @param value : "The value to clone" - @return "A pointer to the cloned value" - @require $alignof(value) <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'clone_aligned' instead" -*> -macro clone(Allocator allocator, value) @nodiscard -{ - return new(allocator, $typeof(value), value); -} - -<* - @param [&inout] allocator : "The allocator used to clone" - @param slice : "The slice to clone" - @return "A pointer to the cloned slice" - - @require $kindof(slice) == SLICE || $kindof(slice) == ARRAY -*> -macro clone_slice(Allocator allocator, slice) @nodiscard -{ - if (!lengthof(slice)) return {}; - - var $Type = $typeof(slice[0]); - - $Type[] new_arr = new_array(allocator, $Type, slice.len); - mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof); - - return new_arr; -} - -<* - Clone overaligned values. Must be released using free_aligned. - - @param [&inout] allocator : "The allocator to use to clone" - @param value : "The value to clone" - @return "A pointer to the cloned value" -*> -macro clone_aligned(Allocator allocator, value) @nodiscard -{ - return new_aligned(allocator, $typeof(value), value)!!; -} - -fn any clone_any(Allocator allocator, any value) @nodiscard -{ - usz size = value.type.sizeof; - void* data = malloc(allocator, size); - mem::copy(data, value.ptr, size); - return any_make(data, value.type); -} - - -<* - @require bytes > 0 - @require alignment > 0 - @require bytes <= isz.max -*> -macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment) -{ - if (alignment < void*.alignof) alignment = void*.alignof; - usz header = AlignedBlock.sizeof + alignment; - usz alignsize = bytes + header; - $if $kindof(#alloc_fn(bytes)) == OPTIONAL: - void* data = #alloc_fn(alignsize)!; - $else - void* data = #alloc_fn(alignsize); - $endif - void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment); - AlignedBlock* desc = (AlignedBlock*)mem - 1; - assert(mem > data); - *desc = { bytes, data }; - return mem; -} - -<* - @require bytes > 0 - @require alignment > 0 - @require bytes <= isz.max -*> -macro void*? @aligned_alloc_fn(context, #alloc_fn, usz bytes, usz alignment) -{ - if (alignment < void*.alignof) alignment = void*.alignof; - usz header = AlignedBlock.sizeof + alignment; - usz alignsize = bytes + header; - $if $kindof(#alloc_fn(context, bytes)) == OPTIONAL: - void* data = #alloc_fn(context, alignsize)!; - $else - void* data = #alloc_fn(context, alignsize); - $endif - void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment); - AlignedBlock* desc = (AlignedBlock*)mem - 1; - assert(mem > data); - *desc = { bytes, data }; - return mem; -} - -struct AlignedBlock -{ - usz len; - void* start; -} - -macro void? @aligned_free(#free_fn, void* old_pointer) -{ - AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; - $if $kindof(#free_fn(desc.start)) == OPTIONAL: - #free_fn(desc.start)!; - $else - #free_fn(desc.start); - $endif -} - -macro void? @aligned_free_fn(context, #free_fn, void* old_pointer) -{ - AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; - $if $kindof(#free_fn(context, desc.start)) == OPTIONAL: - #free_fn(context, desc.start)!; - $else - #free_fn(context, desc.start); - $endif -} - -<* - @require bytes > 0 - @require alignment > 0 -*> -macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment) -{ - AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; - void* data_start = desc.start; - void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!; - mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); - $if $kindof(#free_fn(data_start)) == OPTIONAL: - #free_fn(data_start)!; - $else - #free_fn(data_start); - $endif - return new_data; -} - -<* - @require bytes > 0 - @require alignment > 0 -*> -macro void*? @aligned_realloc_fn(context, #calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment) -{ - AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; - void* data_start = desc.start; - void* new_data = @aligned_alloc_fn(context, #calloc_fn, bytes, alignment)!; - mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); - $if $kindof(#free_fn(context, data_start)) == OPTIONAL: - #free_fn(context, data_start)!; - $else - #free_fn(context, data_start); - $endif - return new_data; -} - -// All allocators -alias mem @builtin = thread_allocator ; -tlocal Allocator thread_allocator @private = base_allocator(); -Allocator temp_base_allocator @private = base_allocator(); - -typedef PoolState = TempAllocator*; - -const LazyTempAllocator LAZY_TEMP @private = {}; -tlocal Allocator current_temp = &LAZY_TEMP; -tlocal TempAllocator* top_temp; -tlocal bool auto_create_temp = false; - -usz temp_allocator_min_size = temp_allocator_default_min_size(); -usz temp_allocator_reserve_size = temp_allocator_default_reserve_size(); -usz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4; - -fn PoolState push_pool(usz reserve = 0) -{ - Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand(); - current_temp = ((TempAllocator*)old).derive_allocator(reserve)!!; - return (PoolState)old.ptr; -} - -fn void pop_pool(PoolState old) -{ - TempAllocator* temp = (TempAllocator*)old; - current_temp = temp; - temp.reset(); -} - -macro Allocator base_allocator() @private -{ - $if env::LIBC: - return &allocator::LIBC_ALLOCATOR; - $else - return &allocator::NULL_ALLOCATOR; - $endif -} - -macro usz temp_allocator_size() @local -{ - $switch env::MEMORY_ENV: - $case NORMAL: return 256 * 1024; - $case SMALL: return 1024 * 32; - $case TINY: return 1024 * 4; - $case NONE: return 0; - $endswitch -} - -macro usz temp_allocator_default_min_size() @local -{ - $switch env::MEMORY_ENV: - $case NORMAL: return 16 * 1024; - $case SMALL: return 1024 * 2; - $case TINY: return 256; - $case NONE: return 256; - $endswitch -} - -macro usz temp_allocator_default_reserve_size() @local -{ - $switch env::MEMORY_ENV: - $case NORMAL: return 1024; - $case SMALL: return 128; - $case TINY: return 64; - $case NONE: return 64; - $endswitch -} - -macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator; - -<* - @require !top_temp : "This should never be called when temp already exists" -*> -fn Allocator create_temp_allocator_on_demand() @private -{ - if (!auto_create_temp) - { - auto_create_temp = true; - abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread."); - } - return create_temp_allocator(temp_base_allocator, temp_allocator_size(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size); -} - -<* - @require !top_temp : "This should never be called when temp already exists" -*> -fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, usz min_size, usz realloc_size) @private -{ - return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!; -} - -macro Allocator temp() @deprecated("Use 'tmem' instead") -{ - return current_temp; -} - -alias tmem @builtin = current_temp; - -fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM) -{ - auto_create_temp = true; -} - -fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC) -{ - destroy_temp_allocators(); -} - -<* - Call this to destroy any memory used by the temp allocators. This will invalidate all temp memory. -*> -fn void destroy_temp_allocators() -{ - if (!top_temp) return; - top_temp.free(); - top_temp = null; - current_temp = &LAZY_TEMP; -} - -import libc; -typedef LazyTempAllocator (Allocator) @private = uptr; - -fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic -{ - if (!top_temp) create_temp_allocator_on_demand(); - return top_temp.acquire(bytes, init_type, alignment); -} - -fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic -{ - if (!top_temp) create_temp_allocator_on_demand(); - return top_temp.resize(old_ptr, new_bytes, alignment); -} - -fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic -{ -} - -const NullAllocator NULL_ALLOCATOR = {}; -typedef NullAllocator (Allocator) = uptr; - -fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic -{ - return mem::OUT_OF_MEMORY~; -} - -fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic -{ - return mem::OUT_OF_MEMORY~; -} - -fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic -{ -} - diff --git a/test/stdlib/src/core/mem_mempool.c3 b/test/stdlib/src/core/mem_mempool.c3 index be366ff..42ce0ce 100644 --- a/test/stdlib/src/core/mem_mempool.c3 +++ b/test/stdlib/src/core/mem_mempool.c3 @@ -8,7 +8,7 @@ struct FixedBlockPoolNode { void* buffer; FixedBlockPoolNode *next; - usz capacity; + sz capacity; } struct FixedBlockPoolEntry @@ -32,12 +32,12 @@ struct FixedBlockPool FixedBlockPoolNode *tail; void *next_free; void *freelist; - usz block_size; - usz grow_capacity; - usz allocated; - usz page_size; - usz alignment; - usz used; + sz block_size; + sz grow_capacity; + sz allocated; + sz page_size; + sz alignment; + sz used; bool initialized; } @@ -54,14 +54,14 @@ struct FixedBlockPool @require calculate_actual_capacity(capacity, block_size) * block_size >= block_size : "Total memory would overflow" *> -fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0) +fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, sz block_size, sz capacity = INITIAL_CAPACITY, sz alignment = 0) { self.allocator = allocator; self.tail = &self.head; self.head.next = null; - self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof); + self.block_size = math::max(block_size, FixedBlockPoolEntry::size); capacity = calculate_actual_capacity(capacity, self.block_size); - self.alignment = allocator::alignment_for_allocation(alignment); + self.alignment = allocators::alignment_for_allocation(alignment); self.page_size = capacity * self.block_size; assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity); self.head.buffer = fixedblockpool_allocate_page(self); @@ -86,9 +86,9 @@ fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_siz @param capacity : "The amount of blocks to be pre-allocated" @require !self.initialized : "The block pool must not be initialized" *> -macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY) +macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, sz capacity = INITIAL_CAPACITY) { - return self.init(allocator, $Type.sizeof, capacity, $Type.alignof); + return self.init(allocator, $Type::size, capacity, $Type::alignment); } <* @@ -98,7 +98,7 @@ macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $ @param capacity : "The amount of blocks to be pre-allocated" @require !self.initialized : "The block pool must not be initialized" *> -macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity); +macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, sz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity); <* Initialize an block pool using Temporary allocator @@ -107,7 +107,7 @@ macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = @param capacity : "The amount of blocks to be pre-allocated" @require !self.initialized : "The block pool must not be initialized" *> -macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity); +macro FixedBlockPool* FixedBlockPool.tinit(&self, sz block_size, sz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity); <* Free up the entire block pool @@ -130,7 +130,7 @@ fn void FixedBlockPool.free(&self) fixedblockpool_free_page(self, iter.buffer); FixedBlockPoolNode* current = iter; iter = iter.next; - allocator::free(self.allocator, current); + alloc::free(self.allocator, current); } self.initialized = false; self.allocated = 0; @@ -212,7 +212,7 @@ fn bool fixedblockpool_check_ptr(FixedBlockPool* self, void *ptr) @local *> fn void fixedblockpool_new_node(FixedBlockPool* self) @local { - FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode); + FixedBlockPoolNode* node = alloc::new(self.allocator, FixedBlockPoolNode); node.buffer = fixedblockpool_allocate_page(self); $if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER: asan::poison_memory_region(node.buffer, self.page_size); @@ -227,23 +227,23 @@ fn void fixedblockpool_new_node(FixedBlockPool* self) @local macro void* fixedblockpool_allocate_page(FixedBlockPool* self) @private { return self.alignment > mem::DEFAULT_MEM_ALIGNMENT - ? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!! - : allocator::calloc(self.allocator, self.page_size); + ? alloc::calloc_aligned(self.allocator, self.page_size, self.alignment)!! + : alloc::calloc(self.allocator, self.page_size); } macro void fixedblockpool_free_page(FixedBlockPool* self, void* page) @private { if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT) { - allocator::free_aligned(self.allocator, page); + alloc::free_aligned(self.allocator, page); } else { - allocator::free(self.allocator, page); + alloc::free(self.allocator, page); } } -macro usz calculate_actual_capacity(usz capacity, usz block_size) @private +macro sz calculate_actual_capacity(sz capacity, sz block_size) @private { // Assume some overhead if (capacity) return capacity; diff --git a/test/stdlib/src/core/os/mem_vm.c3 b/test/stdlib/src/core/os/mem_vm.c3 index 617eef4..f1d67e3 100644 --- a/test/stdlib/src/core/os/mem_vm.c3 +++ b/test/stdlib/src/core/os/mem_vm.c3 @@ -11,7 +11,7 @@ import std::io, std::os::win32, std::os::posix, libc; struct VirtualMemory { void* ptr; - usz size; + sz size; VirtualMemoryAccess default_access; } @@ -29,10 +29,10 @@ enum VirtualMemoryAccess ANY } -fn usz aligned_alloc_size(usz size) +fn sz aligned_alloc_size(sz size) { $if env::WIN32: - return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity(); + return size > 0 ? mem::aligned_offset(size, (sz)win32::allocation_granularity()) : (sz)win32::allocation_granularity(); $else return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize(); $endif @@ -46,22 +46,22 @@ fn usz aligned_alloc_size(usz size) @return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS @return "Pointer to the allocated memory, page aligned" *> -fn void*? alloc(usz size, VirtualMemoryAccess access) +fn void*? alloc(sz size, VirtualMemoryAccess access) { $switch: $case env::POSIX: - void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0); + void* ptr = posix::mmap(null, (usz)aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0); if (ptr != posix::MAP_FAILED) return ptr; switch (libc::errno()) { - case errno::ENOMEM: return mem::OUT_OF_MEMORY~; - case errno::EOVERFLOW: return RANGE_OVERFLOW~; - case errno::EPERM: return ACCESS_DENIED~; - case errno::EINVAL: return INVALID_ARGS~; - default: return UNKNOWN_ERROR~; + case ENOMEM: return mem::OUT_OF_MEMORY~; + case EOVERFLOW: return RANGE_OVERFLOW~; + case EPERM: return ACCESS_DENIED~; + case EINVAL: return INVALID_ARGS~; + default: return UNKNOWN_ERROR~; } $case env::WIN32: - void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32()); + void* ptr = win32::virtualAlloc(null, (Win32_SIZE_T)aligned_alloc_size(size), MEM_RESERVE, access.to_win32()); if (ptr) return ptr; switch (win32::getLastError()) { @@ -81,17 +81,17 @@ fn void*? alloc(usz size, VirtualMemoryAccess access) @param size : "The size of the allocated pointer" @require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned" *> -fn void? release(void* ptr, usz size) +fn void? release(void* ptr, sz size) { $switch: $case env::POSIX: - if (posix::munmap(ptr, aligned_alloc_size(size))) + if (posix::munmap(ptr, (usz)aligned_alloc_size(size))) { switch (libc::errno()) { - case errno::EINVAL: return INVALID_ARGS~; // Not a valid mapping or size - case errno::ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped - default: return RELEASE_FAILED~; + case EINVAL: return INVALID_ARGS~; // Not a valid mapping or size + case ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped + default: return RELEASE_FAILED~; } } $case env::WIN32: @@ -117,22 +117,22 @@ fn void? release(void* ptr, usz size) @require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned" @return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS *> -fn void? protect(void* ptr, usz len, VirtualMemoryAccess access) +fn void? protect(void* ptr, sz len, VirtualMemoryAccess access) { $switch: $case env::POSIX: - if (!posix::mprotect(ptr, len, access.to_posix())) return; + if (!posix::mprotect(ptr, (usz)len, access.to_posix())) return; switch (libc::errno()) { - case errno::EACCES: return ACCESS_DENIED~; - case errno::EINVAL: return UNALIGNED_ADDRESS~; - case errno::EOVERFLOW: return RANGE_OVERFLOW~; - case errno::ENOMEM: return UNMAPPED_ACCESS~; - default: return UPDATE_FAILED~; + case EACCES: return ACCESS_DENIED~; + case EINVAL: return UNALIGNED_ADDRESS~; + case EOVERFLOW: return RANGE_OVERFLOW~; + case ENOMEM: return UNMAPPED_ACCESS~; + default: return UPDATE_FAILED~; } $case env::WIN32: Win32_Protect old; - if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return; + if (win32::virtualProtect(ptr, (Win32_SIZE_T)len, access.to_win32(), &old)) return; switch (win32::getLastError()) { case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~; @@ -155,13 +155,13 @@ fn void? protect(void* ptr, usz len, VirtualMemoryAccess access) @require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned" @return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS *> -fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE) +fn void? commit(void* ptr, sz len, VirtualMemoryAccess access = READWRITE) { $switch: $case env::POSIX: return protect(ptr, len, READWRITE) @inline; $case env::WIN32: - void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32()); + void* result = win32::virtualAlloc(ptr, (Win32_SIZE_T)len, MEM_COMMIT, access.to_win32()); if (result) return; switch (win32::getLastError()) { @@ -189,22 +189,22 @@ fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE) @require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned" @return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS *> -fn void? decommit(void* ptr, usz len, bool block = true) +fn void? decommit(void* ptr, sz len, bool block = true) { $switch: $case env::POSIX: - if (posix::madvise(ptr, len, posix::MADV_DONTNEED)) + if (posix::madvise(ptr, (usz)len, posix::MADV_DONTNEED)) { switch (libc::errno()) { - case errno::EINVAL: return UNALIGNED_ADDRESS~; - case errno::ENOMEM: return UNMAPPED_ACCESS~; - default: return UPDATE_FAILED~; + case EINVAL: return UNALIGNED_ADDRESS~; + case ENOMEM: return UNMAPPED_ACCESS~; + default: return UPDATE_FAILED~; } } if (block) (void)protect(ptr, len, PROTECTED) @inline; $case env::WIN32: - if (!win32::virtualFree(ptr, len, MEM_DECOMMIT)) + if (!win32::virtualFree(ptr, (Win32_SIZE_T)len, MEM_DECOMMIT)) { switch (win32::getLastError()) { @@ -230,22 +230,22 @@ fn void? decommit(void* ptr, usz len, bool block = true) @return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND @return "Pointer to the mapped region" *> -fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX) +fn void*? mmap_file(Fd fd, sz size, sz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX) { CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE; - void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset); + void* ptr = posix::mmap(null, (usz)aligned_alloc_size(size), access.to_posix(), flags, fd, offset); if (ptr != posix::MAP_FAILED) return ptr; switch (libc::errno()) { - case errno::ENOMEM: return mem::OUT_OF_MEMORY~; - case errno::EOVERFLOW: return RANGE_OVERFLOW~; - case errno::EPERM: return ACCESS_DENIED~; - case errno::EINVAL: return INVALID_ARGS~; - case errno::EACCES: return io::NO_PERMISSION~; - case errno::EBADF: return io::FILE_NOT_VALID~; - case errno::EAGAIN: return io::WOULD_BLOCK~; - case errno::ENXIO: return io::FILE_NOT_FOUND~; - default: return UNKNOWN_ERROR~; + case ENOMEM: return mem::OUT_OF_MEMORY~; + case EOVERFLOW: return RANGE_OVERFLOW~; + case EPERM: return ACCESS_DENIED~; + case EINVAL: return INVALID_ARGS~; + case EACCES: return io::NO_PERMISSION~; + case EBADF: return io::FILE_NOT_VALID~; + case EAGAIN: return io::WOULD_BLOCK~; + case ENXIO: return io::FILE_NOT_FOUND~; + default: return UNKNOWN_ERROR~; } } @@ -256,7 +256,7 @@ fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access @require size > 0 : "The size must be non-zero" @return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS *> -fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED) +fn VirtualMemory? virtual_alloc(sz size, VirtualMemoryAccess access = PROTECTED) { size = aligned_alloc_size(size); void* ptr = alloc(size, access)!; @@ -274,7 +274,7 @@ fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED @require offset + len <= self.size : "Length out of range" @return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR *> -macro void? VirtualMemory.commit(self, usz offset, usz len) +macro void? VirtualMemory.commit(self, sz offset, sz len) { return commit(self.ptr + offset, len, self.default_access); } @@ -290,7 +290,7 @@ macro void? VirtualMemory.commit(self, usz offset, usz len) @require offset + len < self.size : "Length out of range" @return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR *> -macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access) +macro void? VirtualMemory.protect(self, sz offset, sz len, VirtualMemoryAccess access) { return protect(self.ptr + offset, len, access); } @@ -306,7 +306,7 @@ macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess @require offset + len < self.size : "Length out of range" @return? UPDATE_FAILED *> -fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true) +fn void? VirtualMemory.decommit(self, sz offset, sz len, bool block = true) { return decommit(self.ptr + offset, len, block); } diff --git a/test/stdlib/src/core/os/wasm/wasm_memory.c3 b/test/stdlib/src/core/os/wasm/wasm_memory.c3 index 944ff5f..9974a2d 100644 --- a/test/stdlib/src/core/os/wasm/wasm_memory.c3 +++ b/test/stdlib/src/core/os/wasm/wasm_memory.c3 @@ -1,30 +1,30 @@ -module std::core::mem::allocator; +module std::core::mem::allocators; -const usz WASM_BLOCK_SIZE = 65536; +const sz WASM_BLOCK_SIZE = 65536; WasmMemory wasm_memory; struct WasmMemory { - usz allocation; - uptr use; + sz allocation; + sz use; } -fn char[]? WasmMemory.allocate_block(&self, usz bytes) +fn char[]? WasmMemory.allocate_block(&self, sz bytes) { if (!self.allocation) { self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE; } - isz bytes_required = bytes + self.use - self.allocation; + sz bytes_required = bytes + self.use - self.allocation; if (bytes_required <= 0) { defer self.use += bytes; return ((char*)self.use)[:bytes]; } - usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE; + sz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE; if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY~; self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE; defer self.use += bytes; diff --git a/test/stdlib/src/core/private/allocators_heap.c3 b/test/stdlib/src/core/private/allocators_heap.c3 new file mode 100644 index 0000000..92c3ea8 --- /dev/null +++ b/test/stdlib/src/core/private/allocators_heap.c3 @@ -0,0 +1,12 @@ +module std::core::mem::allocators @private; + +tlocal Allocator thread_allocator = base_allocator(); +macro Allocator base_allocator() +{ + $if env::LIBC: + return &LIBC_ALLOCATOR; + $else + return &NULL_ALLOCATOR; + $endif +} + diff --git a/test/stdlib/src/core/private/allocators_temp.c3 b/test/stdlib/src/core/private/allocators_temp.c3 new file mode 100644 index 0000000..4dab129 --- /dev/null +++ b/test/stdlib/src/core/private/allocators_temp.c3 @@ -0,0 +1,62 @@ +module std::core::mem::allocators @private; + +Allocator temp_base_allocator @private = base_allocator(); +const LazyTempAllocator LAZY_TEMP @private = {}; + +sz temp_allocator_min_size = temp_allocator_default_min_size(); +sz temp_allocator_reserve_size = temp_allocator_default_reserve_size(); +sz temp_allocator_realloc_size = temp_allocator_default_min_size() * 4; + +// Default temp allocator sizes +macro sz temp_allocator_size() @local +{ + $switch env::MEMORY_ENV: + $case NORMAL: return 256 * 1024; + $case SMALL: return 1024 * 32; + $case TINY: return 1024 * 4; + $case NONE: return 0; + $endswitch +} + +macro sz temp_allocator_default_min_size() @local +{ + $switch env::MEMORY_ENV: + $case NORMAL: return 16 * 1024; + $case SMALL: return 1024 * 2; + $case TINY: return 256; + $case NONE: return 256; + $endswitch +} + +macro sz temp_allocator_default_reserve_size() @local +{ + $switch env::MEMORY_ENV: + $case NORMAL: return 1024; + $case SMALL: return 128; + $case TINY: return 64; + $case NONE: return 64; + $endswitch +} + +<* + @require !top_temp : "This should never be called when temp already exists" +*> +fn Allocator create_temp_allocator_on_demand() @private +{ + if (!auto_create_temp) + { + auto_create_temp = true; + abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread."); + } + return create_temp_allocator(temp_base_allocator, temp_allocator_size(), temp_allocator_reserve_size, temp_allocator_min_size, temp_allocator_realloc_size); +} + +fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM) +{ + auto_create_temp = true; +} + +fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC) +{ + destroy_temp_allocators(); +} \ No newline at end of file diff --git a/test/stdlib/src/core/private/cpu_detect.c3 b/test/stdlib/src/core/private/cpu_detect.c3 index 784e9fe..d5802a5 100644 --- a/test/stdlib/src/core/private/cpu_detect.c3 +++ b/test/stdlib/src/core/private/cpu_detect.c3 @@ -6,8 +6,8 @@ struct CpuId } fn CpuId x86_cpuid(uint eax, uint ecx = 0) { - int edx; - int ebx; + uint edx; + uint ebx; asm { movl $eax, eax; diff --git a/test/stdlib/src/core/private/macho_runtime.c3 b/test/stdlib/src/core/private/macho_runtime.c3 index eafa7c9..10ebb98 100644 --- a/test/stdlib/src/core/private/macho_runtime.c3 +++ b/test/stdlib/src/core/private/macho_runtime.c3 @@ -59,7 +59,7 @@ const LC_SEGMENT_64 = 0x19; fn bool name_cmp(char* a, char[16]* b) { - for (usz i = 0; i < 16; i++) + for (sz i = 0; i < 16; i++) { if (a[i] != (*b)[i]) return false; if (a[i] == '\0') return true; @@ -69,7 +69,7 @@ fn bool name_cmp(char* a, char[16]* b) fn SegmentCommand64*? find_segment(MachHeader* header, char* segname) { - LoadCommand* command = (void*)header + MachHeader64.sizeof; + LoadCommand* command = (void*)header + MachHeader64::size; for (uint i = 0; i < header.ncmds; i++) { if (command.cmd == LC_SEGMENT_64) @@ -83,7 +83,7 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname) } fn Section64*? find_section(SegmentCommand64* command, char* sectname) { - Section64* section = (void*)command + SegmentCommand64.sizeof; + Section64* section = (void*)command + SegmentCommand64::size; for (uint i = 0; i < command.nsects; i++) { if (name_cmp(sectname, §ion.sectname)) return section; @@ -101,10 +101,10 @@ macro find_segment_section_body(MachHeader* header, char* segname, char* sectnam return ($Type[]){}; } $Type* ptr = (void*)header + section.offset; - return ptr[:section.size / $Type.sizeof]; + return ptr[:section.size / $Type::size]; } -alias DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide); +alias DyldCallback = fn void (MachHeader* mh, sz vmaddr_slide); extern fn void _dyld_register_func_for_add_image(DyldCallback); @@ -119,8 +119,8 @@ struct DlInfo extern fn void printf(char*, ...); extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo); -extern fn void* realloc(void* ptr, usz size); -extern fn void* malloc(usz size); +extern fn void* realloc(void* ptr, sz size); +extern fn void* malloc(sz size); extern fn void free(void* ptr); alias CallbackFn = fn void(); @@ -208,15 +208,15 @@ struct TypeId char type; TypeId* parentof; DynamicMethod* dtable; - usz sizeof; + sz sizeof; TypeId* inner; - usz len; + sz len; typeid[*] additional; } -fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide) +fn void dl_reg_callback(MachHeader* mh, sz vmaddr_slide) { - usz size = 0; + sz size; assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state); foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod)) { diff --git a/test/stdlib/src/core/private/main_stub.c3 b/test/stdlib/src/core/private/main_stub.c3 index 73e0a97..7b95498 100644 --- a/test/stdlib/src/core/private/main_stub.c3 +++ b/test/stdlib/src/core/private/main_stub.c3 @@ -1,8 +1,8 @@ module std::core::main_stub; -macro usz _strlen(ptr) @private +macro sz _strlen(ptr) @private { - usz len = 0; + sz len; while (ptr[len]) len++; return len; } @@ -35,7 +35,6 @@ macro String[] args_to_strings(int argc, char** argv) @private for (int i = 0; i < argc; i++) { char* arg = argv[i]; - usz len = 0; list[i] = (String)arg[:_strlen(arg)]; } return list; diff --git a/test/stdlib/src/core/refcount.c3 b/test/stdlib/src/core/refcount.c3 index ed25c4f..339597c 100644 --- a/test/stdlib/src/core/refcount.c3 +++ b/test/stdlib/src/core/refcount.c3 @@ -11,36 +11,38 @@ module std::core::mem::ref ; import std::thread, std::atomic; -const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT; +const OVERALIGNED @private = Type::alignment > mem::DEFAULT_MEM_ALIGNMENT; alias DeallocFn = fn void(void*); fn Ref wrap(Type* ptr, Allocator allocator = mem) { - return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator }; + return { .refcount = alloc::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator }; } <* - @require $vacount < 2 : "Too many arguments." - @require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type" + @param #init : "The optional initializer" + + @require !$defined(#init) ||| $defined(Type a = #init) : "#init must be an initializer for the type" + @return "A pointer to data of type Type." *> -macro Ref new(..., Allocator allocator = mem) +macro Ref new(Allocator allocator, #init = ...) @nodiscard @safemacro { - - $switch: - $case OVERALIGNED && !$vacount: - Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!; - $case OVERALIGNED: - Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!; - *ptr = $vaexpr[0]; - $case !$vacount: - Type* ptr = allocator::calloc(allocator, Type.sizeof); - $default: - Type* ptr = allocator::malloc(allocator, Type.sizeof); - *ptr = $vaexpr[0]; - $endswitch - return { .refcount = allocator::new(allocator, Atomic{int}), - .ptr = ptr, - .allocator = allocator }; + $if $defined(#init): + $if OVERALIGNED: + Type* ptr = alloc::malloc_aligned(allocator, Type::size, Type::alignment)!!; + *ptr = #init; + $else + Type* ptr = alloc::malloc(allocator, Type::size); + *ptr = #init; + $endif + $else + $if OVERALIGNED: + Type* ptr = alloc::calloc_aligned(allocator, Type::size, Type::alignment)!!; + $else + Type* ptr = alloc::calloc(allocator, Type::size); + $endif + $endif + return { .refcount = alloc::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator }; } struct Ref @@ -69,11 +71,11 @@ fn void Ref.release(&self) self.ptr.dealloc(); $endif $if OVERALIGNED: - allocator::free_aligned(self.allocator, self.ptr); + alloc::free_aligned(self.allocator, self.ptr); $else - allocator::free(self.allocator, self.ptr); + alloc::free(self.allocator, self.ptr); $endif - allocator::free(self.allocator, self.refcount); + alloc::free(self.allocator, self.refcount); *self = {}; } } diff --git a/test/stdlib/src/core/runtime.c3 b/test/stdlib/src/core/runtime.c3 index 41e2e01..8f474f7 100644 --- a/test/stdlib/src/core/runtime.c3 +++ b/test/stdlib/src/core/runtime.c3 @@ -19,21 +19,13 @@ struct AnyRaw struct SliceRaw { void* ptr; - usz len; + sz len; } -macro @enum_lookup($Type, #value, value) +macro @enum_lookup($Type, $name, value) { - $foreach $val : $Type.values: - if ($val.#value == value) return $val; - $endforeach - return NOT_FOUND~; -} - -macro @enum_lookup_new($Type, $name, value) -{ - $foreach $val : $Type.values: - if ($val.$eval($name) == value) return $val; + $foreach $val : $Type::values: + if ($val.$name == value) return $val; $endforeach return NOT_FOUND~; } diff --git a/test/stdlib/src/core/runtime_benchmark.c3 b/test/stdlib/src/core/runtime_benchmark.c3 index f2f438a..9c433ff 100644 --- a/test/stdlib/src/core/runtime_benchmark.c3 +++ b/test/stdlib/src/core/runtime_benchmark.c3 @@ -1,4 +1,4 @@ -module std::core::runtime; +module std::core::runtime @if(env::BENCHMARKING); import libc, std::time, std::io, std::sort, std::math, std::collections; const DEFAULT_WARMUP_ITERS @local = 3; @@ -22,18 +22,18 @@ fn void bench_init_context() @init struct BenchContext { - uint warmup_iterations; - uint max_iterations; - HashMap { String, uint } fn_iters; - HashMap { String, uint } module_iters; - HashMap { String, uint } module_warmup; + int warmup_iterations; + int max_iterations; + HashMap { String, int } fn_iters; + HashMap { String, int } module_iters; + HashMap { String, int } module_warmup; Clock clock; NanoDuration nano_seconds; long cycle_start; long cycle_stop; DString log; bool is_warming; - uint this_iteration; + int this_iteration; bool stop; bool has_ansi_codes; bool generate_csv; @@ -49,7 +49,7 @@ fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator) { BenchmarkFn[] fns = $$BENCHMARK_FNS; String[] names = $$BENCHMARK_NAMES; - BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len); + BenchmarkUnit[] benchmarks = alloc::alloc_array(allocator, BenchmarkUnit, names.len); foreach (i, benchmark : fns) { @@ -69,30 +69,30 @@ fn double get_stddev(NanoDuration[] set, double mean) @local return math::sqrt(sum_sq_diff / (double)set.len); } -fn void set_benchmark_module_warmup_iterations(String module_name, uint value) @builtin +fn void set_benchmark_module_warmup_iterations(String module_name, int value) @builtin { if (!bench_context.module_warmup.is_initialized()) bench_context.module_warmup.init(mem); bench_context.module_warmup[module_name] = value; } -fn void set_benchmark_module_max_iterations(String module_name, uint value) @builtin +fn void set_benchmark_module_max_iterations(String module_name, int value) @builtin { assert(value > 0); if (!bench_context.module_iters.is_initialized()) bench_context.module_iters.init(mem); bench_context.module_iters[module_name] = value; } -macro void set_benchmark_warmup_iterations(uint value) @builtin +macro void set_benchmark_warmup_iterations(int value) @builtin { set_benchmark_module_warmup_iterations($$MODULE, value); } -macro void set_benchmark_max_iterations(uint value) @builtin +macro void set_benchmark_max_iterations(int value) @builtin { set_benchmark_module_max_iterations($$MODULE, value); } -fn void set_benchmark_func_iterations(String func, uint value) @builtin +fn void set_benchmark_func_iterations(String func, int value) @builtin { assert(value > 0); if (!bench_context.fn_iters.is_initialized()) bench_context.fn_iters.init(mem); @@ -161,7 +161,7 @@ fn void append_duration_colored(DString *ds, NanoDuration dur, bool ansi, String static String[] colors = { "\e[0;96m", "\e[0;92m", "\e[0;93m", "\e[0;91m" }; double val = (double)dur; - usz idx = 0; + sz idx = 0; while (val > 1000.0 && idx < 3) { val /= 1000.0; @@ -181,7 +181,7 @@ fn bool run_benchmarks(String[] args, BenchmarkUnit[] benchmarks) BenchContext* ctx = &bench_context; ctx.has_ansi_codes = terminal_has_ansi_codes(); - for (usz i = 1; i < args.len; i++) + for (sz i = 1; i < args.len; i++) { switch (args[i]) { @@ -197,9 +197,9 @@ fn bool run_benchmarks(String[] args, BenchmarkUnit[] benchmarks) } } - usz max_name; + sz max_name; foreach (&unit : benchmarks) max_name = max(max_name, unit.name.len); - usz len = max_name + 9; + sz len = max_name + 9; DString name = dstring::temp_with_capacity(64); name.append_repeat('-', len / 2); @@ -224,8 +224,8 @@ fn bool run_benchmarks(String[] args, BenchmarkUnit[] benchmarks) module_name = module_name[:pos]; } - uint current_unit_iterations = ctx.fn_iters[unit.name] ?? ctx.module_iters[module_name] ?? ctx.max_iterations; - uint current_warmup_iterations = ctx.module_warmup[module_name] ?? ctx.warmup_iterations; + int current_unit_iterations = ctx.fn_iters[unit.name] ?? ctx.module_iters[module_name] ?? ctx.max_iterations; + int current_warmup_iterations = ctx.module_warmup[module_name] ?? ctx.warmup_iterations; if (current_unit_iterations == 0) { @@ -242,7 +242,7 @@ fn bool run_benchmarks(String[] args, BenchmarkUnit[] benchmarks) io::printf("%s ", name.str_view()); ctx.is_warming = true; - for (uint i = 0; i < current_warmup_iterations; i++) + for (int i = 0; i < current_warmup_iterations; i++) { unit.func() @inline; } @@ -253,15 +253,15 @@ fn bool run_benchmarks(String[] args, BenchmarkUnit[] benchmarks) char[] perc_str = { [0..19] = ' ', [20] = 0 }; int perc = 0; - uint print_step = current_unit_iterations / 100; + int print_step = current_unit_iterations / 100; if (print_step == 0) print_step = 1; for (ctx.this_iteration = 0; ctx.this_iteration < current_unit_iterations; ++ctx.this_iteration, ctx.nano_seconds = {}) { if (ctx.this_iteration % print_step == 0) // only print right about when the % will update { - perc_str[0..(uint)math::floor((ctx.this_iteration / (float)current_unit_iterations) * 20)] = '#'; - perc = (uint)math::ceil(100 * (ctx.this_iteration / (float)current_unit_iterations)); + perc_str[0..(int)math::floor((ctx.this_iteration / (float)current_unit_iterations) * 20)] = '#'; + perc = (int)math::ceil(100 * (ctx.this_iteration / (float)current_unit_iterations)); io::printf( ctx.has_ansi_codes diff --git a/test/stdlib/src/core/runtime_test.c3 b/test/stdlib/src/core/runtime_test.c3 index 1b4d8a6..52415ac 100644 --- a/test/stdlib/src/core/runtime_test.c3 +++ b/test/stdlib/src/core/runtime_test.c3 @@ -3,8 +3,8 @@ // a copy of which can be found in the LICENSE_STDLIB file. module std::core::runtime; import std::core::test @public; -import std::core::mem::allocator @public; -import libc, std::time, std::io, std::sort, std::os; +import std::core::mem::allocators @public; +import libc, std::time, std::io, std::sort, std::os, std::thread; alias TestFn = fn void(); @@ -27,14 +27,14 @@ struct TestContext bool is_in_panic; bool is_quiet_mode; bool is_no_capture; - bool sort; - bool check_leaks; + bool sort; + bool check_leaks; String current_test_name; TestFn setup_fn; TestFn teardown_fn; char* error_buffer; - usz error_buffer_capacity; + sz error_buffer_capacity; File fake_stdout; struct stored { @@ -54,7 +54,7 @@ fn TestUnit[] test_collection_create(Allocator allocator) { TestFn[] fns = $$TEST_FNS; String[] names = $$TEST_NAMES; - TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len); + TestUnit[] tests = alloc::alloc_array(allocator, TestUnit, names.len); foreach (i, test : fns) { tests[i] = { names[i], fns[i] }; @@ -65,13 +65,13 @@ fn TestUnit[] test_collection_create(Allocator allocator) // Sort the tests by their name in ascending order. fn int cmp_test_unit(TestUnit a, TestUnit b) { - usz an = a.name.len; - usz bn = b.name.len; + sz an = a.name.len; + sz bn = b.name.len; if (an > bn) @swap(a, b); foreach (i, ac : a.name) { char bc = b.name[i]; - if (ac != bc) return an > bn ? bc - ac : ac - bc; + if (ac != bc) return an > bn ? (int)bc - (int)ac : (int)ac - (int)bc; } return (int)(an - bn); } @@ -100,11 +100,11 @@ fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POS panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context)); } -fn void test_panic(String message, String file, String function, uint line) @local +fn void test_panic(String message, String file, String function, int line) @local { panic_test(message, file, function, line); } -fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local +fn void panic_test(String message, String file, String function, int line, void* extra_trace = null) @local { if (test_context.is_in_panic) return; test_context.is_in_panic = true; @@ -114,7 +114,7 @@ fn void panic_test(String message, String file, String function, uint line, void if (test_context.assert_print_backtrace) { $if env::NATIVE_STACKTRACE: - builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace); + builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace); $endif } io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message); @@ -131,7 +131,7 @@ fn void panic_test(String message, String file, String function, uint line, void } test_context.is_in_panic = false; - allocator::thread_allocator = test_context.stored.allocator; + allocators::thread_allocator = test_context.stored.allocator; libc::longjmp(&test_context.buf, 1); } @@ -139,10 +139,10 @@ fn void mute_output() @local { if (test_context.is_no_capture || !test_context.fake_stdout.file) return; File* stdout = io::stdout(); - File* stderr = io::stderr(); + File* stderr = io::stderr(); *stderr = test_context.fake_stdout; *stdout = test_context.fake_stdout; - (void)test_context.fake_stdout.set_cursor(0)!!; + (void)test_context.fake_stdout.seek(0)!!; } fn void unmute_output(bool has_error) @local @@ -155,7 +155,7 @@ fn void unmute_output(bool has_error) @local *stderr = test_context.stored.stderr; *stdout = test_context.stored.stdout; - ulong log_size = test_context.fake_stdout.cursor()!!; + long log_size = test_context.fake_stdout.cursor()!!; if (has_error) { io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]"); @@ -165,7 +165,7 @@ fn void unmute_output(bool has_error) @local { test_context.fake_stdout.write_byte('\n')!!; test_context.fake_stdout.write_byte('\0')!!; - test_context.fake_stdout.set_cursor(0)!!; + test_context.fake_stdout.seek(0)!!; io::printfn("\n========== TEST LOG ============"); io::printfn("%s\n", test_context.current_test_name); @@ -186,7 +186,8 @@ fn void unmute_output(bool has_error) @local fn bool run_tests(String[] args, TestUnit[] tests) @private { - usz max_name; + Thread main = thread::current(); + sz max_name; if (!tests.len) { io::printn("There are no test units to run."); @@ -204,8 +205,8 @@ $endif { .assert_print_backtrace = true, .breakpoint_on_assert = false, - .sort = true, - .check_leaks = true, + .sort = true, + .check_leaks = true, .log_level = LogPriority.ERROR, .test_filter = "", .has_ansi_codes = terminal_has_ansi_codes(), @@ -284,7 +285,7 @@ $endif int tests_skipped = 0; int test_count = tests.len; DString name = dstring::temp_with_capacity(64); - usz len = max_name + 9; + sz len = max_name + 9; name.append_repeat('-', len / 2); name.append(" TESTS "); name.append_repeat('-', len - len / 2); @@ -321,19 +322,21 @@ $endif mem.init(context.stored.allocator); if (libc::setjmp(&context.buf) == 0) { + // We might arrive here from a different thread + if (!thread::current().equals(main)) thread::exit(-1); mute_output(); mem.clear(); - if (context.check_leaks) allocator::thread_allocator = &mem; + if (context.check_leaks) allocators::thread_allocator = &mem; @pool() - { - unit.func(); - }; + { + unit.func(); + }; // track cleanup that may take place in teardown_fn if (context.teardown_fn) { context.teardown_fn(); } - if (context.check_leaks) allocator::thread_allocator = context.stored.allocator; + if (context.check_leaks) allocators::thread_allocator = context.stored.allocator; unmute_output(false); // all good, discard output if (mem.has_leaks()) diff --git a/test/stdlib/src/core/slice2d.c3 b/test/stdlib/src/core/slice2d.c3 index 757e639..aa9763a 100644 --- a/test/stdlib/src/core/slice2d.c3 +++ b/test/stdlib/src/core/slice2d.c3 @@ -7,17 +7,17 @@ module std::core::array; struct Slice2d { Type* ptr; - usz inner_len; - usz ystart; - usz ylen; - usz xstart; - usz xlen; + sz inner_len; + sz ystart; + sz ylen; + sz xstart; + sz xlen; } <* @return `The length of the "outer" slice` *> -fn usz Slice2d.len(&self) @operator(len) +fn sz Slice2d.len(&self) @operator(len) { return self.ylen; } @@ -25,7 +25,7 @@ fn usz Slice2d.len(&self) @operator(len) <* @return `The total number of elements.` *> -fn usz Slice2d.count(&self) +fn sz Slice2d.count(&self) { return self.ylen * self.xlen; } @@ -33,7 +33,7 @@ fn usz Slice2d.count(&self) <* Step through each element of the slice. *> -macro void Slice2d.@each(&self; @body(usz[<2>], Type)) +macro void Slice2d.@each(&self; @body(sz[<2>], Type)) { foreach (y, line : *self) { @@ -47,7 +47,7 @@ macro void Slice2d.@each(&self; @body(usz[<2>], Type)) <* Step through each element of the slice *by reference* *> -macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*)) +macro void Slice2d.@each_ref(&self; @body(sz[<2>], Type*)) { foreach (y, line : *self) { @@ -65,7 +65,7 @@ macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*)) @return "The slice for the particular row" @require idy >= 0 && idy < self.ylen *> -macro Type[] Slice2d.get_row(self, usz idy) @operator([]) +macro Type[] Slice2d.get_row(self, sz idy) @operator([]) { return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen]; } @@ -78,7 +78,7 @@ macro Type[] Slice2d.get_row(self, usz idy) @operator([]) @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" *> -macro Type Slice2d.get_coord(self, usz[<2>] coord) +macro Type Slice2d.get_coord(self, sz[<2>] coord) { return *self.get_coord_ref(coord); } @@ -91,7 +91,7 @@ macro Type Slice2d.get_coord(self, usz[<2>] coord) @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" *> -macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord) +macro Type* Slice2d.get_coord_ref(self, sz[<2>] coord) { return self.get_xy_ref(coord.x, coord.y); } @@ -132,7 +132,7 @@ macro Type* Slice2d.get_xy_ref(self, x, y) @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" *> -macro void Slice2d.set_coord(self, usz[<2>] coord, Type value) +macro void Slice2d.set_coord(self, sz[<2>] coord, Type value) { *self.get_coord_ref(coord) = value; } @@ -161,7 +161,7 @@ macro void Slice2d.set_xy(self, x, y, Type value) @require y >= 0 && y < self.ylen @require x >= 0 && x < self.xlen *> -fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0) +fn Slice2d Slice2d.slice(&self, sz x = 0, sz xlen = 0, sz y = 0, sz ylen = 0) { if (xlen < 1) xlen = self.xlen + xlen; if (ylen < 1) ylen = self.ylen + ylen; diff --git a/test/stdlib/src/core/string.c3 b/test/stdlib/src/core/string.c3 index c3b712e..8d3de51 100644 --- a/test/stdlib/src/core/string.c3 +++ b/test/stdlib/src/core/string.c3 @@ -1,7 +1,6 @@ module std::core::string; -import std::io, std::ascii; -import std::core::mem::allocator; - +import std::io, std::math; +import std::core::string::conv; typedef String @constinit @if(!$defined(String)) = inline char[]; @@ -21,7 +20,7 @@ typedef ZString @constinit = inline char*; will be UTF-8 encoded, so there is no need to use the wchar_t versions of functions outside of encoding functions. *> -typedef WString = inline Char16*; +typedef WString @constinit = inline Char16*; <* Char32 is a UTF32 codepoint @@ -129,7 +128,7 @@ fn String bformat(char[] buffer, String fmt, args...) @format(1) }; char[] buffer_copy = buffer; f.init(format_fn, &buffer_copy); - usz len = f.vprintf(fmt, args)!!; + sz len = f.vprintf(fmt, args)!!; return (String)buffer[:len]; } @@ -171,22 +170,22 @@ fn String join(Allocator allocator, String[] s, String joiner) { if (!s) { - return (String)allocator::new_array(allocator, char, 2)[:0]; + return (String)alloc::new_array(allocator, char, 2)[:0]; } - usz joiner_len = joiner.len; - usz total_size = joiner_len * (s.len - 1) + 1; + sz joiner_len = joiner.len; + sz total_size = joiner_len * (s.len - 1) + 1; foreach (String* &str : s) { total_size += str.len; } - char[] data = allocator::alloc_array(allocator, char, total_size); - usz offset = s[0].len; + char[] data = alloc::alloc_array(allocator, char, total_size); + sz offset = s[0].len; data[:offset] = s[0][:offset]; foreach (String* &str : s[1..]) { data[offset:joiner_len] = joiner[:joiner_len]; offset += joiner_len; - usz len = str.len; + sz len = str.len; data[offset:len] = str.[:len]; offset += len; }; @@ -266,8 +265,8 @@ fn String String.trim(self, String to_trim = " \n\t\r\f\v") *> fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET) { - usz start = 0; - usz len = self.len; + sz start = 0; + sz len = self.len; while (start < len && to_trim.contains(self[start])) start++; while (len > start && to_trim.contains(self[len - 1])) len--; return self[start..len - 1]; @@ -283,8 +282,8 @@ fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET *> fn String String.trim_left(self, String to_trim = " \n\t\r\f\v") { - usz start = 0; - usz len = self.len; + sz start = 0; + sz len = self.len; while (start < len && char_in_set(self[start], to_trim)) start++; if (start == len) return self[:0]; return self[start..]; @@ -300,7 +299,7 @@ fn String String.trim_left(self, String to_trim = " \n\t\r\f\v") *> fn String String.trim_right(self, String to_trim = " \n\t\r\f\v") { - usz len = self.len; + sz len = self.len; while (len > 0 && char_in_set(self[len - 1], to_trim)) len--; return self[:len]; } @@ -320,6 +319,28 @@ fn bool String.starts_with(self, String prefix) return self[:prefix.len] == prefix; } +fn int String.compare_to_ignore_case(self, String s) +{ + sz len = min(s.len, self.len); + for (sz i = 0; i < len; i++) + { + sz sign = math::sign(self[i].to_upper() - s[i].to_upper()); + if (sign != 0) return (int)sign; + } + return (int)math::sign(self.len - s.len); +} + +fn int String.compare_to(self, String s) +{ + sz len = min(s.len, self.len); + for (sz i = 0; i < len; i++) + { + sz sign = math::sign(self[i] - s[i]); + if (sign != 0) return (int)sign; + } + return (int)math::sign(self.len - s.len); +} + <* Check if the String ends with the suffix. @@ -343,7 +364,7 @@ fn bool String.ends_with(self, String suffix) @pure @return `the substring with the prefix removed` *> -fn String String.strip(self, String prefix) +fn String String.strip_prefix(self, String prefix) { if (!prefix.len || !self.starts_with(prefix)) return self; return self[prefix.len..]; @@ -357,7 +378,7 @@ fn String String.strip(self, String prefix) @pure @return `the substring with the suffix removed` *> -fn String String.strip_end(self, String suffix) +fn String String.strip_suffix(self, String suffix) { if (!suffix.len || !self.ends_with(suffix)) return self; // Note that this is the safe way if we want to support zero length. @@ -376,15 +397,15 @@ fn String String.strip_end(self, String suffix) @require delimiter.len > 0 : "The delimiter must be at least 1 character long" @ensure return.len > 0 || skip_empty *> -fn String[] String.split(self, Allocator allocator, String delimiter, usz max = 0, bool skip_empty = false) +fn String[] String.split(self, Allocator allocator, String delimiter, sz max = 0, bool skip_empty = false) { - usz capacity = 16; - usz i = 0; - String* holder = allocator::alloc_array(allocator, String, capacity); + sz capacity = 16; + sz i = 0; + String* holder = alloc::alloc_array(allocator, String, capacity); bool no_more = false; while (!no_more) { - usz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter); + sz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter); String res @noinit; if (try index) { @@ -404,7 +425,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max = if (i == capacity) { capacity *= 2; - holder = allocator::realloc(allocator, holder, String.sizeof * capacity); + holder = alloc::realloc(allocator, holder, String::size * capacity); } holder[i++] = res; } @@ -421,7 +442,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max = @param max : "Max number of elements, 0 means no limit, defaults to 0" @param skip_empty : "True to skip empty elements" *> -fn String[] String.tsplit(s, String delimiter, usz max = 0, bool skip_empty = false) => s.split(tmem, delimiter, max, skip_empty) @inline; +fn String[] String.tsplit(s, String delimiter, sz max = 0, bool skip_empty = false) => s.split(tmem, delimiter, max, skip_empty) @inline; faultdef BUFFER_EXCEEDED; @@ -436,14 +457,14 @@ faultdef BUFFER_EXCEEDED; @ensure return.len > 0 || skip_empty @return? BUFFER_EXCEEDED : `If there are more elements than would fit the buffer` *> -fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz max = 0, bool skip_empty = false) +fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, sz max = 0, bool skip_empty = false) { - usz max_capacity = buffer.len; - usz i = 0; + sz max_capacity = buffer.len; + sz i = 0; bool no_more = false; while (!no_more) { - usz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter); + sz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter); String res @noinit; if (try index) { @@ -504,10 +525,10 @@ fn bool String.contains_char(s, char character) @pure @return "The number of times matched" *> -fn usz String.count(self, String substr) +fn sz String.count(self, String substr) { - usz count = 0; - usz needed = substr.len; + sz count = 0; + sz needed = substr.len; if (needed == 0) return 0; char first = substr[0]; while OUTER: (self.len >= needed) @@ -536,7 +557,7 @@ fn usz String.count(self, String substr) @return "the index of the character" @return? NOT_FOUND : "if the character cannot be found" *> -fn usz? String.index_of_char(self, char character) +fn sz? String.index_of_char(self, char character) { foreach (i, c : self) { @@ -555,7 +576,7 @@ fn usz? String.index_of_char(self, char character) @return "the index of the character" @return? NOT_FOUND : "if the character cannot be found" *> -fn usz? String.index_of_chars(String self, char[] characters) +fn sz? String.index_of_chars(String self, char[] characters) { foreach (i, c : self) { @@ -579,11 +600,11 @@ fn usz? String.index_of_chars(String self, char[] characters) @return "the index of the character" @return? NOT_FOUND : "if the character cannot be found starting from the start_index" *> -fn usz? String.index_of_char_from(self, char character, usz start_index) +fn sz? String.index_of_char_from(self, char character, sz start_index) { - usz len = self.len; + sz len = self.len; if (len <= start_index) return NOT_FOUND~; - for (usz i = start_index; i < len; i++) + for (sz i = start_index; i < len; i++) { if (self[i] == character) return i; } @@ -600,7 +621,7 @@ fn usz? String.index_of_char_from(self, char character, usz start_index) @return "the index of the character" @return? NOT_FOUND : "if the character cannot be found" *> -fn usz? String.rindex_of_char(self, char character) +fn sz? String.rindex_of_char(self, char character) { foreach_r (i, c : self) { @@ -620,16 +641,16 @@ fn usz? String.rindex_of_char(self, char character) @return "the index of the substring" @return? NOT_FOUND : "if the substring cannot be found" *> -fn usz? String.index_of(self, String substr) +fn sz? String.index_of(self, String substr) { - usz needed = substr.len; - if (needed > 0 && self.len >= needed) + sz needed = substr.len; + if (!needed || self.len < needed) return NOT_FOUND~; + if (needed == 1) return self.index_of_char(substr[0]) @inline; + + char first = substr[0]; + foreach (i, c: self[..^needed]) { - char first = substr[0]; - foreach (i, c: self[..^needed]) - { - if (c == first && self[i : needed] == substr) return i; - } + if (c == first && self[i : needed] == substr) return i; } return NOT_FOUND~; } @@ -645,9 +666,9 @@ fn usz? String.index_of(self, String substr) @return "the index of the substring" @return? NOT_FOUND : "if the substring cannot be found" *> -fn usz? String.rindex_of(self, String substr) +fn sz? String.rindex_of(self, String substr) { - usz needed = substr.len; + sz needed = substr.len; if (needed > 0 && self.len >= needed) { char first = substr[0]; @@ -678,9 +699,9 @@ fn String ZString.str_view(self) return (String)(self[:self.len()]); } -fn usz ZString.char_len(str) +fn sz ZString.char_len(str) { - usz len = 0; + sz len = 0; char* ptr = (char*)str; while (char c = ptr++[0]) { @@ -689,24 +710,24 @@ fn usz ZString.char_len(str) return len; } -fn usz ZString.len(self) +fn sz ZString.len(self) { - usz len; + sz len; for (char* ptr = (char*)self; *ptr; ptr++) len++; return len; } -fn usz WString.len(self) +fn sz WString.len(self) { - usz len; + sz len; for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++; return len; } fn ZString String.zstr_copy(self, Allocator allocator) { - usz len = self.len; - char* str = allocator::malloc(allocator, len + 1); + sz len = self.len; + char* str = alloc::malloc(allocator, len + 1); mem::copy(str, self.ptr, len); str[len] = 0; return (ZString)str; @@ -714,9 +735,9 @@ fn ZString String.zstr_copy(self, Allocator allocator) fn String String.concat(self, Allocator allocator, String s2) { - usz full_len = self.len + s2.len; - char* str = allocator::malloc(allocator, full_len + 1); - usz self_len = self.len; + sz full_len = self.len + s2.len; + char* str = alloc::malloc(allocator, full_len + 1); + sz self_len = self.len; mem::copy(str, self.ptr, self_len); mem::copy(str + self_len, s2.ptr, s2.len); str[full_len] = 0; @@ -735,8 +756,8 @@ fn ZString String.zstr_tcopy(self) => self.zstr_copy(tmem) @inline; *> fn String String.copy(self, Allocator allocator) { - usz len = self.len; - char* str = allocator::malloc(allocator, len + 1); + sz len = self.len; + char* str = alloc::malloc(allocator, len + 1); mem::copy(str, self.ptr, len); str[len] = 0; return (String)str[:len]; @@ -745,7 +766,7 @@ fn String String.copy(self, Allocator allocator) fn void String.free(&self, Allocator allocator) { if (!self.ptr) return; - allocator::free(allocator, self.ptr); + alloc::free(allocator, self.ptr); *self = ""; } @@ -768,8 +789,8 @@ fn String ZString.tcopy(self) *> fn Char16[]? String.to_utf16(self, Allocator allocator) { - usz len16 = conv::utf16len_for_utf8(self); - Char16* data = allocator::alloc_array_try(allocator, Char16, len16 + 1)!; + sz len16 = conv::utf16len_for_utf8(self); + Char16* data = alloc::alloc_array_try(allocator, Char16, len16 + 1)!; conv::utf8to16_unsafe(self, data)!; data[len16] = 0; return data[:len16]; @@ -786,8 +807,8 @@ fn WString? String.to_temp_wstring(self) => self.to_wstring(tmem); fn Char32[]? String.to_utf32(self, Allocator allocator) { - usz codepoints = conv::utf8_codepoints(self); - Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!; + sz codepoints = conv::utf8_codepoints(self); + Char32* data = alloc::alloc_array_try(allocator, Char32, codepoints + 1)!; conv::utf8to32_unsafe(self, data)!; data[codepoints] = 0; return data[:codepoints]; @@ -863,8 +884,8 @@ fn String String.capitalize_copy(self, Allocator allocator) fn String String.snake_to_pascal_copy(self, Allocator allocator) { Splitter splitter = self.tokenize("_"); - char[] new_string = allocator::alloc_array(allocator, char, self.len + 1); - usz index = 0; + char[] new_string = alloc::alloc_array(allocator, char, self.len + 1); + sz index = 0; while (try s = splitter.next()) { assert(s.len > 0); @@ -888,7 +909,7 @@ fn void String.convert_snake_to_pascal(&self) { Splitter splitter = self.tokenize("_"); String new_string = *self; - usz index = 0; + sz index = 0; while (try s = splitter.next()) { assert(s.len > 0); @@ -911,8 +932,8 @@ fn void String.convert_snake_to_pascal(&self) fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool() { DString d; - d.init(tmem, (usz)(self.len * 1.5)); - usz index = 0; + d.init(tmem, (sz)(self.len * 1.5)); + sz index = 0; foreach (i, c : self) { if (c.is_upper()) @@ -946,27 +967,72 @@ fn String String.to_upper_tcopy(self) fn String? from_utf32(Allocator allocator, Char32[] utf32) { - usz len = conv::utf8len_for_utf32(utf32); - char* data = allocator::malloc_try(allocator, len + 1)!; - defer catch allocator::free(allocator, data); - conv::utf32to8_unsafe(utf32, data); + sz len = conv::utf8len_for_utf32(utf32); + char* data = alloc::malloc_try(allocator, len + 1)!; + defer catch alloc::free(allocator, data); + conv::utf32to8_unsafe(utf32, data, $unaligned: false, $byteswap: false); + data[len] = 0; + return (String)data[:len]; +} + +fn String? from_utf32_bytes(Allocator allocator, char[] utf32, bool swap_endianess) +{ + sz len = conv::utf8len_for_utf32_bytes(utf32, swap_endianess); + char* data = alloc::malloc_try(allocator, len + 1)!; + defer catch alloc::free(allocator, data); + bool is_aligned = (uptr)utf32.ptr % 4 == 0; + Char32[] unaligned = ((Char32*)utf32.ptr)[:utf32.len / 4]; + switch + { + case is_aligned && !swap_endianess: + conv::utf32to8_unsafe(unaligned, data, $unaligned: false, $byteswap: false); + case !is_aligned && !swap_endianess: + conv::utf32to8_unsafe(unaligned, data, $unaligned: true, $byteswap: false); + case is_aligned && swap_endianess: + conv::utf32to8_unsafe(unaligned, data, $unaligned: false, $byteswap: true); + default: + conv::utf32to8_unsafe(unaligned, data, $unaligned: true, $byteswap: true); + } data[len] = 0; return (String)data[:len]; } fn String? from_utf16(Allocator allocator, Char16[] utf16) { - usz len = conv::utf8len_for_utf16(utf16); - char* data = allocator::malloc_try(allocator, len + 1)!; - defer catch allocator::free(allocator, data); - conv::utf16to8_unsafe(utf16, data)!; + sz len = conv::utf8len_for_utf16(utf16); + char* data = alloc::malloc_try(allocator, len + 1)!; + defer catch alloc::free(allocator, data); + conv::utf16to8_unsafe(utf16, data, $unaligned: false, $byteswap: false)!; + data[len] = 0; + return (String)data[:len]; +} + +fn String? from_utf16_bytes(Allocator allocator, char[] utf16, bool swap_endianess) +{ + sz len = conv::utf8len_for_utf16_bytes(utf16, swap_endianess); + char* data = alloc::malloc_try(allocator, len + 1)!; + defer catch alloc::free(allocator, data); + + bool is_aligned = (uptr)utf16.ptr % 2 == 0; + Char16[] unaligned = ((Char16*)utf16.ptr)[:utf16.len / 2]; + switch + { + case is_aligned && !swap_endianess: + conv::utf16to8_unsafe(unaligned, data, $unaligned: false, $byteswap: false)!; + case !is_aligned && !swap_endianess: + conv::utf16to8_unsafe(unaligned, data, $unaligned: true, $byteswap: false)!; + case is_aligned && swap_endianess: + conv::utf16to8_unsafe(unaligned, data, $unaligned: false, $byteswap: true)!; + default: + conv::utf16to8_unsafe(unaligned, data, $unaligned: true, $byteswap: true)!; + } data[len] = 0; return (String)data[:len]; } fn String? from_wstring(Allocator allocator, WString wstring) { - usz utf16_len; + sz utf16_len; while (wstring[utf16_len] != 0) utf16_len++; Char16[] utf16 = wstring[:utf16_len]; return from_utf16(allocator, utf16); @@ -975,9 +1041,9 @@ fn String? from_wstring(Allocator allocator, WString wstring) fn String? tfrom_wstring(WString wstring) => from_wstring(tmem, wstring) @inline; fn String? tfrom_utf16(Char16[] utf16) => from_utf16(tmem, utf16) @inline; -fn usz String.utf8_codepoints(s) +fn sz String.utf8_codepoints(s) { - usz len = 0; + sz len = 0; foreach (char c : s) { if (c & 0xC0 != 0x80) len++; @@ -985,40 +1051,7 @@ fn usz String.utf8_codepoints(s) return len; } -<* - Determine whether the current string actually points to a ZString-like string. - - This is done by looking at the byte one step after the end of the string. If this - is zero, it is considered zero terminated. - - This function can safely be used with data pointing to null. However, it will not - work correctly if the pointer is invalid, for example it is already freed. -*> -fn bool String.is_zstr(self) @deprecated("Unsafe, use copy instead") -{ - return self.ptr && *(self.ptr + self.len) == 0; -} - -<* - Return a pointer to the string *iff* it is a pointer - to a zero terminated string, otherwise return a temp allocated zstring copy. - - This function is suitable if you are converting strings to ZString on the temp - allocator, but suspect that the String might actually already point to zero - terminated data. - - The function looks one step beyond the end of the slice to determine this, - which means that if that data is then modified after this call, this function - might behave incorrectly. - - For this reason, try to ensure that the resulting ZString is immediately used. - @ensure return[self.len] == 0 -*> -fn ZString String.quick_zstr(self) @deprecated("Unsafe, use zstr_tcopy instead") -{ - return self.zstr_tcopy(); -} <* @@ -1028,7 +1061,6 @@ fn ZString String.quick_zstr(self) @deprecated("Unsafe, use zstr_tcopy instead") Furthermore it will skip any spaces before and after the number. - @param $Type : "The type to convert to" @param base : "The base to convert to" @require base > 0 && base <= 16 : "Unsupported base" @return? MALFORMED_INTEGER : "When the value has some illegal character" @@ -1036,10 +1068,10 @@ fn ZString String.quick_zstr(self) @deprecated("Unsafe, use zstr_tcopy instead") @return? EMPTY_STRING : "If the string was empty" @return? NEGATIVE_VALUE : "If the type was unsigned, and the value had a - prefix" *> -macro String.to_integer(self, $Type, int base = 10) +fn IntType? to_integer(String self, int base = 10) { - usz len = self.len; - usz index = 0; + sz len = self.len; + sz index = 0; char* ptr = self.ptr; while (index < len && ptr[index].is_blank()) index++; if (len == index) return EMPTY_STRING~; @@ -1047,20 +1079,23 @@ macro String.to_integer(self, $Type, int base = 10) switch (self[index]) { case '-': - if ($Type.min == 0) return NEGATIVE_VALUE~; - is_negative = true; - index++; + $if IntType::min == 0: + return NEGATIVE_VALUE~; + $else + is_negative = true; + index++; + $endif case '+': index++; default: break; } if (len == index) return MALFORMED_INTEGER~; - $Type base_used = ($Type)base; + IntType base_used = (IntType)base; if (self[index] == '0' && base == 10) { index++; - if (index == len) return ($Type)0; + if (index == len) return (IntType)0; switch (self[index]) { case 'x': @@ -1080,7 +1115,7 @@ macro String.to_integer(self, $Type, int base = 10) } if (len == index) return MALFORMED_INTEGER~; } - $Type value = 0; + IntType value = 0; while (index != len) { char c = self[index++]; @@ -1096,26 +1131,26 @@ macro String.to_integer(self, $Type, int base = 10) { if (is_negative) { - value = value.overflow_mul(base_used).overflow_sub(c) ?? INTEGER_OVERFLOW~!; + value = value.overflow_mul(base_used).overflow_sub((IntType)c) ?? INTEGER_OVERFLOW~!; break; } - value = value.overflow_mul(base_used).overflow_add(c) ?? INTEGER_OVERFLOW~!; + value = value.overflow_mul(base_used).overflow_add((IntType)c) ?? INTEGER_OVERFLOW~!; }; } return value; } -fn int128? String.to_int128(self, int base = 10) => self.to_integer(int128, base); -fn long? String.to_long(self, int base = 10) => self.to_integer(long, base); -fn int? String.to_int(self, int base = 10) => self.to_integer(int, base); -fn short? String.to_short(self, int base = 10) => self.to_integer(short, base); -fn ichar? String.to_ichar(self, int base = 10) => self.to_integer(ichar, base); +fn int128? String.to_int128(self, int base = 10) => to_integer{int128}(self, base) @inline; +fn long? String.to_long(self, int base = 10) => to_integer{long}(self, base) @inline; +fn int? String.to_int(self, int base = 10) => to_integer{int}(self, base) @inline; +fn short? String.to_short(self, int base = 10) => to_integer{short}(self, base) @inline; +fn ichar? String.to_ichar(self, int base = 10) => to_integer{ichar}(self, base) @inline; -fn uint128? String.to_uint128(self, int base = 10) => self.to_integer(uint128, base); -fn ulong? String.to_ulong(self, int base = 10) => self.to_integer(ulong, base); -fn uint? String.to_uint(self, int base = 10) => self.to_integer(uint, base); -fn ushort? String.to_ushort(self, int base = 10) => self.to_integer(ushort, base); -fn char? String.to_uchar(self, int base = 10) => self.to_integer(char, base); +fn uint128? String.to_uint128(self, int base = 10) => to_integer{uint128}(self, base) @inline; +fn ulong? String.to_ulong(self, int base = 10) => to_integer{ulong}(self, base) @inline; +fn uint? String.to_uint(self, int base = 10) => to_integer{uint}(self, base) @inline; +fn ushort? String.to_ushort(self, int base = 10) => to_integer{ushort}(self, base) @inline; +fn char? String.to_char(self, int base = 10) => to_integer{char}(self, base) @inline; fn double? String.to_double(self) => self.to_real(double); fn float? String.to_float(self) => self.to_real(float); @@ -1152,10 +1187,6 @@ fn Splitter String.tokenize_all(self, String split, bool skip_last = false) }; } -fn Splitter String.splitter(self, String split) @deprecated("Use tokenize_all instead") -{ - return self.tokenize_all(split, skip_last: true); -} <* This macro will create a string description of a struct. @@ -1203,7 +1234,7 @@ struct Splitter { String string; String split; - usz current; + sz current; SplitterType type; int last_index; } @@ -1222,24 +1253,35 @@ fn String? Splitter.next(&self) { while (true) { - usz len = self.string.len; - usz current = self.current; + sz len = self.string.len; + sz current = self.current; if (current > len) return NO_MORE_ELEMENT~; if (current == len) { if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT~; self.current++; - return self.string[current - 1:0]; + return self.string[current:0]; } - String remaining = self.string[current..]; - usz? next = remaining.index_of(self.split); + + sz? next; + if (self.split.len == 1) + { + next = self.string.index_of_char_from(self.split[0], current) @inline; + } + else + { + next = self.string[current..].index_of(self.split) @inline + current; + } + if (try next) { - self.current = current + next + self.split.len; - if (!next && self.type == TOKENIZE) continue; - return remaining[:next]; + String res = self.string[current : next - current]; + self.current = next + self.split.len; + if (!res.len && self.type == TOKENIZE) continue; + return res; } + self.current = len + 1; - return remaining; + return self.string[current..]; } } diff --git a/test/stdlib/src/core/string_escape.c3 b/test/stdlib/src/core/string_escape.c3 index 83a9921..d348b99 100644 --- a/test/stdlib/src/core/string_escape.c3 +++ b/test/stdlib/src/core/string_escape.c3 @@ -23,7 +23,7 @@ faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVAL fn String String.escape(String s, Allocator allocator, bool strip_quotes = true) { // Conservative allocation: most strings need minimal escaping - usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes + sz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes if (allocator == tmem) { @@ -88,9 +88,9 @@ fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, @param s : "The string to check" @return "The length needed for the escaped version" *> -fn usz escape_len(String s) +fn sz escape_len(String s) { - usz len = 2; // For quotes + sz len = 2; // For quotes foreach (char c : s) { switch (c) @@ -159,8 +159,8 @@ fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private { - usz len = s.len; - for (usz i = 0; i < len; i++) + sz len = s.len; + for (sz i = 0; i < len; i++) { char c = s[i]; if (c != '\\') @@ -191,9 +191,9 @@ fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, char h1 = s[++i]; char h2 = s[++i]; if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~; - uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0'; + uint val = h1 > '9' ? (h1 | 32u) - 'a' + 10u : h1 - '0'; val = val << 4; - val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0'; + val += h2 > '9' ? (h2 | 32u) - 'a' + 10u : h2 - '0'; result.append_char((char)val); case 'u': // Unicode escape \uHHHH @@ -203,7 +203,7 @@ fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, { char hex_char = s[++i]; if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~; - val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0'); + val = val << 4 + (hex_char > '9' ? (hex_char | 32u) - 'a' + 10u : hex_char - '0'); } result.append_char32(val); case 'U': @@ -214,7 +214,7 @@ fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, { char hex_char = s[++i]; if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~; - val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0'); + val = val << 4 + (hex_char > '9' ? (hex_char | 32u) - 'a' + 10u : hex_char - '0'); } result.append_char32(val); default: diff --git a/test/stdlib/src/core/string_iterator.c3 b/test/stdlib/src/core/string_iterator.c3 index 630683f..0707dd4 100644 --- a/test/stdlib/src/core/string_iterator.c3 +++ b/test/stdlib/src/core/string_iterator.c3 @@ -3,7 +3,7 @@ module std::core::string::iterator; struct StringIterator { String utf8; - usz current; + sz current; } fn void StringIterator.reset(&self) @@ -13,10 +13,10 @@ fn void StringIterator.reset(&self) fn Char32? StringIterator.next(&self) { - usz len = self.utf8.len; - usz current = self.current; + sz len = self.utf8.len; + sz current = self.current; if (current >= len) return NO_MORE_ELEMENT~; - usz read = (len - current < 4 ? len - current : 4); + sz read = (len - current < 4 ? len - current : 4); Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!; self.current += read; return res; @@ -24,10 +24,10 @@ fn Char32? StringIterator.next(&self) fn Char32? StringIterator.peek(&self) { - usz len = self.utf8.len; - usz current = self.current; + sz len = self.utf8.len; + sz current = self.current; if (current >= len) return NO_MORE_ELEMENT~; - usz read = (len - current < 4 ? len - current : 4); + sz read = (len - current < 4 ? len - current : 4); Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!; return res; } @@ -39,10 +39,10 @@ fn bool StringIterator.has_next(&self) fn Char32? StringIterator.get(&self) { - usz len = self.utf8.len; - usz current = self.current; - usz read = (len - current < 4 ? len - current : 4); - usz index = current > read ? current - read : 0; + sz len = self.utf8.len; + sz current = self.current; + sz read = (len - current < 4 ? len - current : 4); + sz index = current > read ? current - read : 0; if (index >= len) return NO_MORE_ELEMENT~; Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!; return res; diff --git a/test/stdlib/src/core/string_to_real.c3 b/test/stdlib/src/core/string_to_real.c3 index d39e6a0..cfb1c7a 100644 --- a/test/stdlib/src/core/string_to_real.c3 +++ b/test/stdlib/src/core/string_to_real.c3 @@ -40,14 +40,14 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign) const uint[2] TH = B1B_MAX; int emax = - $emin - $bits + 3; - const int[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; - usz index; + const uint[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + sz index; bool got_digit = chars[0] == '0'; bool got_rad; long lrp, dc; int k, j, lnz; - usz len = chars.len; - usz last_char = len - 1; + sz len = chars.len; + sz last_char = len - 1; assert(len); @@ -78,7 +78,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign) } } - while (c - '0' < 10u || c == '.') + while ((char)(c - '0') < 10u || c == '.') { switch { @@ -267,7 +267,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign) bool denormal; // Limit precision for denormal results - uint bits = $bits; + int bits = $bits; if (bits > math::DOUBLE_MANT_DIG + e2 - $emin) { bits = math::DOUBLE_MANT_DIG + e2 - $emin; @@ -312,7 +312,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign) y += frac; y -= bias; - if (((e2 + math::DOUBLE_MANT_DIG) & int.max) > emax - 5) + if (((e2 + math::DOUBLE_MANT_DIG) & int::max) > emax - 5) { if (math::abs(y) >= 0x1p53) { @@ -335,9 +335,9 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign) bool got_rad; bool got_digit; bool got_tail; - usz len = chars.len; - usz last_char = len - 1; - usz index; + sz len = chars.len; + sz last_char = len - 1; + sz index; double y; // Skip past first characters @@ -479,8 +479,8 @@ macro String.to_real(chars, $Type) @private chars = chars.trim(); if (!chars.len) return MALFORMED_FLOAT~; - if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf; - if (chars == "NAN" || chars == "nan") return $Type.nan; + if (chars == "infinity" || chars == "INFINITY") return sign * $Type::inf; + if (chars == "NAN" || chars == "nan") return $Type::nan; if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x') { diff --git a/test/stdlib/src/core/test.c3 b/test/stdlib/src/core/test.c3 index 2cad967..9a14dd6 100644 --- a/test/stdlib/src/core/test.c3 +++ b/test/stdlib/src/core/test.c3 @@ -130,7 +130,7 @@ macro eq(left, right) @require delta >= 0, delta <= 1 : "delta must be a small number" @require runtime::test_context != null : "Only allowed in @test functions" *> -macro void eq_approx(double left, double right, uint places = 7, double delta = 0, bool equal_nan = true) +macro void eq_approx(double left, double right, int places = 7, double delta = 0, bool equal_nan = true) { double diff = left - right; double eps = delta; diff --git a/test/stdlib/src/core/types.c3 b/test/stdlib/src/core/types.c3 index 44572c2..3d7a12d 100644 --- a/test/stdlib/src/core/types.c3 +++ b/test/stdlib/src/core/types.c3 @@ -6,9 +6,9 @@ import libc; faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE; <* - @require $Type.kindof.is_int() : "Type was not an integer" + @require $Type::kind.is_int() : "Type was not an integer" @require (bool)v.type : "The value was empty" - @require v.type.kindof == ENUM : "Value was not an enum" + @require v.type.kind == ENUM : "Value was not an enum" *> macro any_to_enum_ordinal(any v, $Type) { @@ -16,21 +16,21 @@ macro any_to_enum_ordinal(any v, $Type) } <* - @require $Type.kindof.is_int() : "Type was not an integer" + @require $Type::kind.is_int() : "Type was not an integer" @require (bool)v.type : "The value was empty" - @require v.type.kindof.is_int() : "Value was not an integer" + @require v.type.kind.is_int() : "Value was not an integer" *> macro any_to_int(any v, $Type) { typeid any_type = v.type; - TypeKind kind = any_type.kindof; - bool is_mixed_signed = $Type.kindof != any_type.kindof; - $Type max = $Type.max; - $Type min = $Type.min; + TypeKind kind = any_type.kind; + bool is_mixed_signed = $Type::kind != any_type.kind; + $Type max = $Type::max; + $Type min = $Type::min; switch (any_type) { case ichar: - ichar c = *(char*)v.ptr; + ichar c = *(ichar*)v.ptr; if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; return ($Type)c; case short: @@ -80,17 +80,17 @@ macro any_to_int(any v, $Type) fn bool typeid.is_subtype_of(self, typeid other) { - while (self != void.typeid) + while (self != void::typeid) { if (self == other) return true; - self = self.parentof; + self = self.parent; } return false; } macro bool is_subtype_of($Type, $OtherType) { - var $typeid = $Type.typeid; + var $typeid = $Type::typeid; $switch $Type: $case $OtherType: return true; $default: return false; @@ -98,10 +98,10 @@ macro bool is_subtype_of($Type, $OtherType) } macro bool is_numerical($Type) { - $switch $Type.kindof: + $switch $Type::kind: $case TYPEDEF: $case CONSTDEF: - return is_numerical($Type.inner); + return is_numerical($Type::inner); $case SIGNED_INT: $case UNSIGNED_INT: $case FLOAT: @@ -122,22 +122,20 @@ macro bool TypeKind.@is_int($kind) @const return $kind == SIGNED_INT ||| $kind == UNSIGNED_INT; } -macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type); - macro bool is_slice_convertible($Type) { - $switch $Type.kindof: + $switch $Type::kind: $case SLICE: return true; $case POINTER: - return $Type.inner.kindof == TypeKind.ARRAY; + return $Type::inner.kind == TypeKind.ARRAY; $default: return false; $endswitch } -macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL; -macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT; +macro bool is_bool($Type) @const => $Type::kind == TypeKind.BOOL; +macro bool is_int($Type) @const => $Type::kind == TypeKind.SIGNED_INT || $Type::kind == TypeKind.UNSIGNED_INT; <* @require is_numerical($Type) : "Expected a numerical type" @@ -149,7 +147,7 @@ macro bool is_signed($Type) @const $case FLOAT: return true; $case VECTOR: - return is_signed($Type.inner); + return is_signed($Type::inner); $default: return false; $endswitch @@ -164,7 +162,7 @@ macro bool is_unsigned($Type) @const $case UNSIGNED_INT: return true; $case VECTOR: - return is_unsigned($Type.inner); + return is_unsigned($Type::inner); $default: return false; $endswitch @@ -172,19 +170,19 @@ macro bool is_unsigned($Type) @const macro typeid flat_type($Type) @const { - $if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: - return flat_type($Type.inner); + $if $Type::kind == TYPEDEF || $Type::kind == CONSTDEF: + return flat_type($Type::inner); $else - return $Type.typeid; + return $Type::typeid; $endif } macro TypeKind flat_kind($Type) @const { - $if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: - return flat_type($Type.inner); + $if $Type::kind == TYPEDEF || $Type::kind == CONSTDEF: + return flat_type($Type::inner); $else - return $Type.kindof; + return $Type::kind; $endif } @@ -200,14 +198,14 @@ macro bool is_ref_indexable($Type) @const macro bool is_flat_intlike($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case SIGNED_INT: $case UNSIGNED_INT: return true; $case VECTOR: $case TYPEDEF: $case CONSTDEF: - return is_flat_intlike($Type.inner); + return is_flat_intlike($Type::inner); $default: return false; $endswitch @@ -215,12 +213,12 @@ macro bool is_flat_intlike($Type) @const macro bool is_intlike($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case SIGNED_INT: $case UNSIGNED_INT: return true; $case VECTOR: - return $Type.inner.kindof == TypeKind.SIGNED_INT || $Type.inner.kindof == TypeKind.UNSIGNED_INT; + return $Type::inner.kind == TypeKind.SIGNED_INT || $Type::inner.kind == TypeKind.UNSIGNED_INT; $default: return false; $endswitch @@ -228,26 +226,26 @@ macro bool is_intlike($Type) @const macro bool is_underlying_int($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case SIGNED_INT: $case UNSIGNED_INT: return true; $case TYPEDEF: - return is_underlying_int($Type.inner); + return is_underlying_int($Type::inner); $default: return false; $endswitch } -macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT; +macro bool is_float($Type) @const => $Type::kind == TypeKind.FLOAT; macro bool is_floatlike($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case FLOAT: return true; $case VECTOR: - return $Type.inner.kindof == TypeKind.FLOAT; + return $Type::inner.kind == TypeKind.FLOAT; $default: return false; $endswitch @@ -255,26 +253,26 @@ macro bool is_floatlike($Type) @const macro bool is_vector($Type) @const { - return $Type.kindof == TypeKind.VECTOR; + return $Type::kind == TypeKind.VECTOR; } macro typeid inner_type($Type) @const { - $if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: - return inner_type($Type.inner); + $if $Type::kind == TYPEDEF || $Type::kind == CONSTDEF: + return inner_type($Type::inner); $else - return $Type.typeid; + return $Type::typeid; $endif } macro TypeKind inner_kind($Type) @const { - return inner_type($Type).kindof; + return inner_type($Type).kind; } macro bool is_same($TypeA, $TypeB) @const { - return $TypeA.typeid == $TypeB.typeid; + return $TypeA::typeid == $TypeB::typeid; } macro bool @has_same(#a, #b, ...) @const @@ -293,7 +291,7 @@ macro bool @has_same(#a, #b, ...) @const macro bool may_load_atomic($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case BOOL: $case SIGNED_INT: $case UNSIGNED_INT: @@ -302,7 +300,7 @@ macro bool may_load_atomic($Type) @const return true; $case TYPEDEF: $case ENUM: - return may_load_atomic($Type.inner); + return may_load_atomic($Type::inner); $default: return false; $endswitch @@ -310,29 +308,29 @@ macro bool may_load_atomic($Type) @const macro lower_to_atomic_compatible_type($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case BOOL: $case SIGNED_INT: $case UNSIGNED_INT: - return $Type.typeid; + return $Type::typeid; $case TYPEDEF: $case CONSTDEF: - return lower_to_atomic_compatible_type($Type.inner); + return lower_to_atomic_compatible_type($Type::inner); $case FLOAT: $switch $Type: $case float16: - return ushort.typeid; + return ushort::typeid; $case float: - return uint.typeid; + return uint::typeid; $case double: - return ulong.typeid; + return ulong::typeid; $case float128: - return uint128.typeid; + return uint128::typeid; $default: - return void.typeid; + return void::typeid; $endswitch $default: - return void.typeid; + return void::typeid; $endswitch } @@ -341,10 +339,10 @@ macro bool is_promotable_to_float($Type) @const => types::is_float($Type) ||| t macro bool is_same_vector_type($Type1, $Type2) @const { - $if $Type1.kindof != TypeKind.VECTOR: - return $Type2.kindof != TypeKind.VECTOR; + $if $Type1::kind != TypeKind.VECTOR: + return $Type2::kind != TypeKind.VECTOR; $else - return $Type1.inner == $Type2.inner && $Type1.len == $Type2.len; + return $Type1::inner == $Type2::inner && $Type1::len == $Type2::len; $endif } @@ -355,7 +353,7 @@ macro bool is_equatable_type($Type) @const $if $defined($Type.less) ||| $defined($Type.compare_to) ||| $defined($Type.equals): return true; $else - return $Type.is_eq; + return $Type::has_equals; $endif } @@ -377,13 +375,10 @@ macro bool @comparable_value(#value) @const $if $defined(#value.less) ||| $defined(#value.compare_to): return true; $else - return $typeof(#value).is_ordered; + return $reflect(#value).is_ordered; $endif } -const CONST_ENUM @builtin @deprecated("Use TypeKind.CONSTDEF instead") = TypeKind.CONSTDEF; -const DISTINCT @builtin @deprecated("Use TypeKind.TYPEDEF instead") = TypeKind.TYPEDEF; - enum TypeKind : char { VOID, @@ -407,11 +402,12 @@ enum TypeKind : char TYPEDEF, POINTER, INTERFACE, + UNTYPEDLIST, } struct TypeEnum { TypeKind type; - usz elements; + sz elements; } diff --git a/test/stdlib/src/core/values.c3 b/test/stdlib/src/core/values.c3 index 25f0d32..3a33a2f 100644 --- a/test/stdlib/src/core/values.c3 +++ b/test/stdlib/src/core/values.c3 @@ -1,12 +1,16 @@ module std::core::values; import std::core::types; +<* @require $defined($reflect(#value).size) : "@alignof only works on types with a definite alignment" *> +macro sz @alignof(#value) @builtin @const => $reflect(#value).alignment; +<* @require $defined($reflect(#value).size) : "@sizeof only works on types with a definite size" *> +macro sz @sizeof(#value) @builtin @const => $reflect(#value).size; +macro TypeKind @kindof(#value) @builtin @const => $typeof(#value)::kind; macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2); <* Return true if two values have the same type before any conversions. *> -macro bool @is_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $typeof(#value1).typeid == $typeof(#value2).typeid; macro bool @is_bool(#value) @const => types::is_bool($typeof(#value)); macro bool @is_int(#value) @const => types::is_int($typeof(#value)); macro bool @is_flat_intlike(#value) @const => types::is_flat_intlike($typeof(#value)); @@ -16,12 +20,6 @@ macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value)); macro bool @is_vector(#value) @const => types::is_vector($typeof(#value)); macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2)); -macro bool @assign_to(#value1, #value2) @const @deprecated("use '$defined(#value1 = #value2)'") => @assignable_to(#value1, $typeof(#value2)); -macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value); -macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'") -{ - return $defined(var $v = #foo); -} macro promote_int(x) { @@ -32,33 +30,14 @@ macro promote_int(x) $endif } -<* - Select between two values at compile time, - the values do not have to be of the same type. - - This acts like `$bool ? #value_1 : #value_2` but at compile time. - - @param $bool : `true for picking the first value, false for the other` - @param #value_1 - @param #value_2 - @returns `The selected value.` -*> -macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.") -{ - $if $bool: - return #value_1; - $else - return #value_2; - $endif -} macro promote_int_same(x, y) { $if @is_int(x): $switch: - $case @is_vector(y) &&& $typeof(y).inner == float.typeid: + $case @is_vector(y) &&& $typeof(y)::inner == float::typeid: return (float)x; - $case $typeof(y).typeid == float.typeid: + $case $typeof(y)::typeid == float::typeid: return (float)x; $default: return (double)x; diff --git a/test/stdlib/src/crypto/aes.c3 b/test/stdlib/src/crypto/aes.c3 index c10faa8..ab299c6 100644 --- a/test/stdlib/src/crypto/aes.c3 +++ b/test/stdlib/src/crypto/aes.c3 @@ -47,12 +47,14 @@ const COLNUM = 4; ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple) CBC - Cipher Block Chaining (Indata be 16 byte multiple) CTR - Counter Mode (Recommended, data may be any size) + CTR_LE - WinZip's Counter Mode (Little-endian counter increment) *> enum BlockMode { ECB, CBC, CTR, + CTR_LE, } <* AES type: 128, 192 or 256 bits *> @@ -66,15 +68,15 @@ enum AesType : (AesKey key) struct AesKey { <* Size of key in bits *> - usz key_size; + sz key_size; <* Size of key in bytes *> int key_len; <* Size of the expanded round_key *> int key_exp_size; // expected size of round_key <* Number of 32 bit words in key *> - usz nk; + sz nk; <* Number of rounds in the cipher *> - usz nr; + sz nr; } struct Aes @@ -290,11 +292,12 @@ fn void bitsliced_mix_columns(Type[8]* q) @private @inline (*q)[7] = t6 ^ s[7] ^ (*q)[7]; } -macro bool is_valid_encryption_len(BlockMode mode, usz len) +macro bool is_valid_encryption_len(BlockMode mode, sz len) { switch (mode) { case CTR: + case CTR_LE: return true; case ECB: case CBC: @@ -312,9 +315,10 @@ fn void Aes.encrypt_buffer(&self, char[] in, char[] out) { switch (self.mode) { - case CTR: ctr_xcrypt_buffer(self, in, out); case ECB: ecb_encrypt_buffer(self, in, out); case CBC: cbc_encrypt_buffer(self, in, out); + case CTR: ctr_xcrypt_buffer(self, in, out); + case CTR_LE: ctr_xcrypt_buffer(self, in, out, &inc_le); } } @@ -331,6 +335,7 @@ fn void Aes.decrypt_buffer(&self, char[] in, char[] out) case ECB: ecb_decrypt_buffer(self, in, out); case CBC: cbc_decrypt_buffer(self, in, out); case CTR: ctr_xcrypt_buffer(self, in, out); + case CTR_LE: ctr_xcrypt_buffer(self, in, out, &inc_le); } } @@ -343,7 +348,7 @@ fn void Aes.decrypt_buffer(&self, char[] in, char[] out) *> fn char[] Aes.encrypt(&self, Allocator allocator, char[] in) { - char[] out = allocator::alloc_array(allocator, char, in.len); + char[] out = alloc::alloc_array(allocator, char, in.len); self.encrypt_buffer(in, out) @inline; return out; } @@ -368,7 +373,7 @@ fn char[] Aes.tencrypt(&self, char[] in) *> fn char[] Aes.decrypt(&self, Allocator allocator, char[] in) { - char[] out = allocator::alloc_array(allocator, char, in.len); + char[] out = alloc::alloc_array(allocator, char, in.len); self.decrypt_buffer(in, out) @inline; return out; } @@ -419,8 +424,8 @@ fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out) *> fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out) { - usz len = in.len; - for (usz i = 0; i < len; i += BLOCKLEN) + sz len = in.len; + for (sz i = 0; i < len; i += BLOCKLEN) { ecb_decrypt_block(aes, (char[16]*)&in[i], (char[16]*)&out[i]) @inline; } @@ -433,8 +438,8 @@ fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out) *> fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out) { - usz len = in.len; - usz i = 0; + sz len = in.len; + sz i = 0; // Parallel path for 4 blocks for (; i + 64 <= len; i += 64) { @@ -468,8 +473,8 @@ fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out) { char[BLOCKLEN] iv = aes.iv; - usz len = in.len; - for (usz i = 0; i < len; i += BLOCKLEN) + sz len = in.len; + for (sz i = 0; i < len; i += BLOCKLEN) { char[BLOCKLEN] tmp = in[i:BLOCKLEN]; xor_with_iv(tmp[..], (char[16]*)&iv); @@ -485,8 +490,8 @@ fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out) *> fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out) { - usz len = in.len; - for (usz i = 0; i < len; i += BLOCKLEN) + sz len = in.len; + for (sz i = 0; i < len; i += BLOCKLEN) { char[BLOCKLEN] tmp; ecb_decrypt_block(aes, in[i:BLOCKLEN], (char[16]*)&tmp); @@ -496,16 +501,47 @@ fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out) } } +alias BlckCounter = fn void(char[]); + +fn void inc_be(char[] iv) +{ + for (int bi = (BLOCKLEN - 1); bi >= 0; bi--) + { + if (iv[bi] == 255) + { + iv[bi] = 0; + continue; + } + iv[bi]++; + break; + } +} + +fn void inc_le(char[] iv) +{ + for (int bi = 0; bi < BLOCKLEN; bi++) + { + if (iv[bi] == 255) + { + iv[bi] = 0; + continue; + } + iv[bi]++; + break; + } +} + + <* @param [&inout] aes : "AES context." @param [in] in : "Plaintext/cipher input." @param [out] out : "Cipher/plaintext output." *> -fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out) +fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out, BlckCounter inc = &inc_be) { char[BLOCKLEN] buffer @noinit; - usz len = in.len; - usz i = 0; + sz len = in.len; + sz i = 0; // Parallel path for 4 blocks for (; i + 64 <= len; i += 64) @@ -515,16 +551,7 @@ fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out) { mem::copy(&counters[b * 16], &aes.iv, 16); // Increment IV for next block - for LOOP: (int bi = (BLOCKLEN - 1); bi >= 0; bi--) - { - if (aes.iv[bi] == 255) - { - aes.iv[bi] = 0; - continue; - } - aes.iv[bi]++; - break LOOP; - } + inc(aes.iv[:16]); } aes_cipher4(aes, &counters, &aes.round_key) @inline; char[<64>] c4; @@ -541,17 +568,7 @@ fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out) { buffer = aes.iv; ecb_encrypt_block(aes, &buffer, &buffer); - - for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--) - { - if (aes.iv[bi] == 255) - { - aes.iv[bi] = 0; - continue; - } - aes.iv[bi]++; - break LOOP; - } + inc(aes.iv[..]); bi = 0; } out[i] = in[i] ^ buffer[bi]; @@ -561,19 +578,18 @@ fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out) fn void transpose(char[16]* in, ushort[8]* q) @private @inline { - ushort q0=0; ushort q1=0; ushort q2=0; ushort q3=0; - ushort q4=0; ushort q5=0; ushort q6=0; ushort q7=0; + ushort q0, q1, q2, q3, q4, q5, q6, q7; for (int i = 0; i < 16; i++) { char b = (*in)[i]; - q0 |= (ushort)((b >> 0) & 1) << i; - q1 |= (ushort)((b >> 1) & 1) << i; - q2 |= (ushort)((b >> 2) & 1) << i; - q3 |= (ushort)((b >> 3) & 1) << i; - q4 |= (ushort)((b >> 4) & 1) << i; - q5 |= (ushort)((b >> 5) & 1) << i; - q6 |= (ushort)((b >> 6) & 1) << i; - q7 |= (ushort)((b >> 7) & 1) << i; + q0 |= (ushort)((b >> 0) & 1u) << i; + q1 |= (ushort)((b >> 1) & 1u) << i; + q2 |= (ushort)((b >> 2) & 1u) << i; + q3 |= (ushort)((b >> 3) & 1u) << i; + q4 |= (ushort)((b >> 4) & 1u) << i; + q5 |= (ushort)((b >> 5) & 1u) << i; + q6 |= (ushort)((b >> 6) & 1u) << i; + q7 |= (ushort)((b >> 7) & 1u) << i; } (*q)[0] = q0; (*q)[1] = q1; (*q)[2] = q2; (*q)[3] = q3; (*q)[4] = q4; (*q)[5] = q5; (*q)[6] = q6; (*q)[7] = q7; @@ -585,14 +601,14 @@ fn void untranspose(ushort[8]* q, char[16]* out) @private @inline ushort q4 = (*q)[4]; ushort q5 = (*q)[5]; ushort q6 = (*q)[6]; ushort q7 = (*q)[7]; for (int i = 0; i < 16; i++) { - char b = (char)((q0 >> i) & 1) << 0; - b |= (char)((q1 >> i) & 1) << 1; - b |= (char)((q2 >> i) & 1) << 2; - b |= (char)((q3 >> i) & 1) << 3; - b |= (char)((q4 >> i) & 1) << 4; - b |= (char)((q5 >> i) & 1) << 5; - b |= (char)((q6 >> i) & 1) << 6; - b |= (char)((q7 >> i) & 1) << 7; + char b = (char)((q0 >> i) & 1u) << 0; + b |= (char)((q1 >> i) & 1u) << 1; + b |= (char)((q2 >> i) & 1u) << 2; + b |= (char)((q3 >> i) & 1u) << 3; + b |= (char)((q4 >> i) & 1u) << 4; + b |= (char)((q5 >> i) & 1u) << 5; + b |= (char)((q6 >> i) & 1u) << 6; + b |= (char)((q7 >> i) & 1u) << 7; (*out)[i] = b; } } @@ -627,13 +643,13 @@ fn void sub_word_ct(char[4]* word) @private fn void transpose4(char[64]* in, ulong[8]* q) @private @inline { - mem::set(q, 0, 8 * ulong.sizeof); + mem::set(q, 0, 8 * ulong::size); for (int i = 0; i < 64; i++) { char v = (*in)[i]; for (int j = 0; j < 8; j++) { - (*q)[j] |= (ulong)((v >> j) & 1) << i; + (*q)[j] |= (ulong)((v >> j) & 1u) << i; } } } @@ -646,13 +662,13 @@ fn void untranspose4(ulong[8]* q, char[64]* out) @private @inline char v = 0; for (int j = 0; j < 8; j++) { - v |= (char)(((*q)[j] >> i) & 1) << j; + v |= (char)(((*q)[j] >> i) & 1u) << j; } (*out)[i] = v; } } -fn void add_round_key_bitsliced(ulong[8]* q, char[] round_key, usz round) @private @inline +fn void add_round_key_bitsliced(ulong[8]* q, char[] round_key, sz round) @private @inline { ushort[8] kq; transpose((char[16]*)&round_key[round * 16], &kq); @@ -671,7 +687,7 @@ fn void aes_cipher4(Aes* aes, char[64]* data, char[] round_key) @private add_round_key_bitsliced(&q, round_key, 0); - for (usz r = 1; r < aes.type.nr; r++) + for (sz r = 1; r < aes.type.nr; r++) { bitsliced_sbox{ulong}(&q); bitsliced_shift_rows{ulong}(&q); @@ -688,7 +704,7 @@ fn void aes_cipher4(Aes* aes, char[64]* data, char[] round_key) @private const char[11] RCON = x`8d01020408102040801b36`; -fn void add_round_key(Aes* aes, usz round, char[] round_key) +fn void add_round_key(Aes* aes, sz round, char[] round_key) { char[<16>] k; mem::copy(&k, &round_key[round * 16], 16); @@ -708,7 +724,7 @@ fn void shift_rows(Aes* aes) fn char[<16>] xtime_vec(char[<16>] x) @local @inline { - return (x << 1) ^ (((x >> 7) & 1) * 0x1b); + return (x << 1) ^ (((x >> 7) & 1u) * 0x1b); } fn void mix_columns(Aes* aes) @@ -725,16 +741,16 @@ fn void mix_columns(Aes* aes) fn char xtime(char x) @local { - return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); + return ((x << 1) ^ (((x >> 7) & 1u) * 0x1b)); } fn char multiply(char x, char y) @local { - return (((y & 1) * x) ^ - (((y>>1) & 1) * xtime(x)) ^ - (((y>>2) & 1) * xtime(xtime(x))) ^ - (((y>>3) & 1) * xtime(xtime(xtime(x)))) ^ - (((y>>4) & 1) * xtime(xtime(xtime(xtime(x)))))); + return (((y & 1u) * x) ^ + (((y>>1) & 1u) * xtime(x)) ^ + (((y>>2) & 1u) * xtime(xtime(x))) ^ + (((y>>3) & 1u) * xtime(xtime(xtime(x)))) ^ + (((y>>4) & 1u) * xtime(xtime(xtime(xtime(x)))))); } fn void inv_mix_columns(Aes* aes) @@ -766,7 +782,7 @@ fn void inv_shift_rows(Aes* aes) fn void aes_cipher(Aes* aes, char[] round_key) { - usz round = 0; + sz round = 0; add_round_key(aes, 0, round_key); for LOOP: (round = 1;; round++) @@ -783,7 +799,7 @@ fn void aes_cipher(Aes* aes, char[] round_key) fn void inv_cipher(Aes* aes, char[] round_key) { add_round_key(aes, aes.type.nr, round_key); - for (usz round = aes.type.nr - 1; ; round--) + for (sz round = aes.type.nr - 1; ; round--) { inv_shift_rows(aes); inv_sub_bytes(aes); @@ -801,8 +817,8 @@ fn void inv_cipher(Aes* aes, char[] round_key) *> fn void key_expansion(AesType type, char[] key, char[] round_key) @private { - usz nk = type.key.nk; - for (usz i = 0; i < nk; i++) + sz nk = type.key.nk; + for (sz i = 0; i < nk; i++) { round_key[(i * 4) + 0] = key[(i * 4) + 0]; round_key[(i * 4) + 1] = key[(i * 4) + 1]; @@ -810,9 +826,9 @@ fn void key_expansion(AesType type, char[] key, char[] round_key) @private round_key[(i * 4) + 3] = key[(i * 4) + 3]; } - for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++) + for (sz i = nk; i < COLNUM * (type.key.nr + 1); i++) { - usz k = (i - 1) * 4; + sz k = (i - 1) * 4; char[4] tempa @noinit; @@ -844,7 +860,7 @@ fn void key_expansion(AesType type, char[] key, char[] round_key) @private sub_word_ct(&tempa); } } - usz j = i * 4; + sz j = i * 4; k = (i - nk) * 4; round_key[j + 0] = round_key[k + 0] ^ tempa[0]; round_key[j + 1] = round_key[k + 1] ^ tempa[1]; diff --git a/test/stdlib/src/crypto/argon2.c3 b/test/stdlib/src/crypto/argon2.c3 new file mode 100644 index 0000000..2f2c7b5 --- /dev/null +++ b/test/stdlib/src/crypto/argon2.c3 @@ -0,0 +1,775 @@ +// Copyright (c) 2026 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// A C3 implementation of the Argon2 memory-hard function in accordance with RFC 9106. +// +module std::crypto::argon2; + +import std::bits, std::encoding::base64, std::hash::blake2, std::io, std::math; +import std::thread; + + +<* Each Argon2 block is a 1024-byte array. *> +const sz BLOCK_SIZE = 1024; + +<* See RFC 9106 - Argon2 uses this constant for its 'version' value `v`. *> +const char VERSION = 0x13; + +<* Whether calls to Argon2 hashing will use parallelization with OS threads. *> +const bool USE_THREADING = !$feature(ARGON2_FORCE_SEQUENTIAL) && (env::POSIX || env::WIN32); + +<* Used for attempts at automatic C3 SIMD optimization with 256-bit vectors. *> +typedef SimdVec @local = ulong[<4>] @simd; + +<* Values for this enumeration are sourced from the spec as variable `y` in the input configuration. *> +constdef Argon2Algorithm : inline uint +{ + ARGON2_D = 0, + ARGON2_I = 1, + ARGON2_ID = 2, +} + +<* Present multiple ways to view the same Argon2 block. *> +union Argon2Block @local @align(SimdVec::size) +{ + SimdVec[BLOCK_SIZE / SimdVec::size] vec; // 1024 / 32 = 32 + uint128[BLOCK_SIZE / uint128::size] z; // 1024 / 16 = 64 + ulong[BLOCK_SIZE / ulong::size] q; // 1024 / 8 = 128 + uint[BLOCK_SIZE / uint::size] d; // 1024 / 4 = 256 + ushort[BLOCK_SIZE / ushort::size] w; // 1024 / 2 = 512 + char[BLOCK_SIZE] b; // 1024 +} + +<* + The Argon2 output, or "tag", is a string T bytes long. + + Argon2 has the following input parameters: + * Message string P, which is a password for password hashing + applications. It MUST have a length not greater than 2^(32)-1 + bytes. + * Nonce S, which is a salt for password hashing applications. It + MUST have a length not greater than 2^(32)-1 bytes. 16 bytes is + RECOMMENDED for password hashing. The salt SHOULD be unique for + each password. + * Degree of parallelism p determines how many independent (but + synchronizing) computational chains (lanes) can be run. It MUST + be an integer value from 1 to 2^(24)-1. + * Tag length T MUST be an integer number of bytes from 4 to 2^(32)-1. + * Memory size m MUST be an integer number of kibibytes from 8*p to + 2^(32)-1. The actual number of blocks is m', which is m rounded + down to the nearest multiple of 4*p. + * Number of passes t (used to tune the running time independently of + the memory size) MUST be an integer number from 1 to 2^(32)-1. + * Version number v MUST be one byte 0x13. + * Secret value K is OPTIONAL. If used, it MUST have a length not + greater than 2^(32)-1 bytes. + * Associated data X is OPTIONAL. If used, it MUST have a length not + greater than 2^(32)-1 bytes. + * Type y MUST be 0 for Argon2d, 1 for Argon2i, or 2 for Argon2id. +*> +struct Argon2Options (Printable) +{ + Argon2Algorithm algorithm; // y + char[] salt; // S (optional) + char[] key; // K (optional) + char[] assoc; // X (optional) + uint lanes; // p + uint mem_size; // m + uint iterations; // t +} + +<* Implement `Printable` interface for the options data structure. *> +fn sz? Argon2Options.to_format(&self, Formatter* f) @dynamic +{ + return f.printf( + "%s[m=%d,t=%d,p=%d,S=%h%s,K=%h%s,X=%h%s]", + self.algorithm, self.mem_size, self.iterations, self.lanes, + self.salt[:min(self.salt.len, 16)], self.salt.len > 16 ? "..." : "", + self.key[:min(self.key.len, 16)], self.key.len > 16 ? "..." : "", + self.assoc[:min(self.assoc.len, 16)], self.assoc.len > 16 ? "..." : "", + ); +} + +<* Allows comparison between two sets of Argon2 options. *> +fn bool Argon2Options.eq(&self, Argon2Options other) @operator(==) +{ + // Do NOT memcmp the two structures, because slices can have the same content in different memory locations. + return self.algorithm == other.algorithm + && self.salt[..] == other.salt[..] + && self.key[..] == other.key[..] + && self.assoc[..] == other.assoc[..] + && self.lanes == other.lanes + && self.mem_size == other.mem_size + && self.iterations == other.iterations; +} + +<* + A simple set of default Argon2 options to work from. + You can use this in a struct-splat to quickly populate settings you don't wish to change. + + ```c3 + Argon2Options my_opts = { ...argon2::DEFAULT_OPTIONS, .salt = my_salt, .mem_size = 64 }; + ``` +*> +const Argon2Options DEFAULT_OPTIONS = { + .algorithm = ARGON2_ID, // a safe default version of the algorithm for non-specific use cases + .lanes = 1, // no parallelism + .mem_size = 4096, // 4096 KiB, or 4 MiB + .iterations = 3, // 3 iterations +}; + +<* Force a value to be little-endian where required. *> +macro uint @le(val) @local => env::BIG_ENDIAN ??? bswap(val) : val; + +<* Force a value to be little-endian where required and return it as a char view. *> +macro @le_c(val) @local => @as_char_view(env::BIG_ENDIAN ??? *&&bswap(val) : val); + +<* The internal round function of Argon2's BLAKE2-like compression function. *> +fn void _g(ulong* a, ulong *b, ulong* c, ulong* d) @local +{ + *a += *b + 2ul * (uint)*a * (uint)*b; + *d ^= *a; + *d = (*d).rotr(32); + *c += *d + 2ul * (uint)*c * (uint)*d; + *b ^= *c; + *b = (*b).rotr(24); + *a += *b + 2ul * (uint)*a * (uint)*b; + *d ^= *a; + *d = (*d).rotr(16); + *c += *d + 2ul * (uint)*c * (uint)*d; + *b ^= *c; + *b = (*b).rotr(63); +} + +<* Apply a set of compression rounds to permute an `Argon2Block` in memory. *> +fn void Argon2Block.permute(&self, ulong[16] v) +{ + _g(&self.q[v[0]], &self.q[v[4]], &self.q[v[8]], &self.q[v[12]]); + _g(&self.q[v[1]], &self.q[v[5]], &self.q[v[9]], &self.q[v[13]]); + _g(&self.q[v[2]], &self.q[v[6]], &self.q[v[10]], &self.q[v[14]]); + _g(&self.q[v[3]], &self.q[v[7]], &self.q[v[11]], &self.q[v[15]]); + _g(&self.q[v[0]], &self.q[v[5]], &self.q[v[10]], &self.q[v[15]]); + _g(&self.q[v[1]], &self.q[v[6]], &self.q[v[11]], &self.q[v[12]]); + _g(&self.q[v[2]], &self.q[v[7]], &self.q[v[8]], &self.q[v[13]]); + _g(&self.q[v[3]], &self.q[v[4]], &self.q[v[9]], &self.q[v[14]]); +} + +<* The core compression function used by Argon2. *> +fn Argon2Block* Argon2Block.compress(&self) @noinline +{ + $for ulong $i = 0; $i < 128; $i += 16: + ulong[16] $indices = {}; + $foreach $x, $c : $indices: $indices[$x] = $i + $x; $endforeach + self.permute($indices); + $endfor + $for ulong $i = 0; $i < 16; $i += 2: + ulong[16] $indices = { 0, 1, 16, 17, 32, 33, 48, 49, 64, 65, 80, 81, 96, 97, 112, 113 }; + $foreach $x, $c : $indices: $indices[$x] = $i + $c; $endforeach + self.permute($indices); + $endfor + return self; +} + +<* + XORs this block with another and returns its pointer for method chaining. + + @param [&in] other : "The block is be XOR'd with this instance." +*> +macro Argon2Block* Argon2Block.xor_with(&self, Argon2Block* other) +{ + array::@zip_into(self.z[..], other.z[..], fn (a, b) => a ^ b); + return self; +} + +<* + Copies another block into this one and returns the destination pointer for method chaining. + + @param [&in] other : "The block to be copied into this instance." +*> +macro Argon2Block* Argon2Block.copy(&self, Argon2Block* other) +{ + self.z[..] = other.z[..]; + return self; +} + +<* An extended BLAKE2 hash function which can apply overlapping permutations to fill a digest space indefinitely. *> +fn void h_prime(char[] digest, char[] input) @local +{ + sz hash_out_len = min(blake2::SIZE_512, digest.len); + Blake2b h @noinit; + h.init(hash_out_len); + h.update(@le_c((uint)digest.len)); // LE32(lengthof T) + h.update(input); // LE32(A) + h.final(digest[:hash_out_len]); + + if (digest.len <= blake2::SIZE_512) return; + + // In an overlapping fashion, walk the storage buffer and place a hash of the partial + // previous value as the stored value of the next. + uint r = (uint)((digest.len + 31) >> 5) - 2u; + sz in = 0; + sz out = 32; + for (sz i = 1; i < r; i++, in += 32, out += 32) + { + digest[out:blake2::SIZE_512] = blake2::b_512(digest[in:blake2::SIZE_512])[..]; + } + + hash_out_len = digest.len - (sz)(32u * r); + if (hash_out_len == 0) return; + + Blake2b k @noinit; + k.init(hash_out_len); + k.update(digest[in:blake2::SIZE_512]); + k.final(digest[out:hash_out_len]); +} + +<* Stores a thread context that's passed to each processing invocation. *> +struct Argon2WorkerContext @local +{ + Argon2Block[] blocks; + Argon2Options* opts_ref; + uint lane; + uint pass; + uint slice; + uint num_blocks; + uint rows; + uint cols; + uint blocks_per_segment; + uint pass_offset; + uint slice_offset; + bool use_constant_time; +} + +<* + Worker thread process for each thread, or just called repeatedly for synchronous Argon2. + + @param [&in] in_ctx : "The current `Argon2WorkerContext` for this slice of work." +*> +fn int process_lane_slice(void* in_ctx) @local +{ + Argon2WorkerContext* ctx = (Argon2WorkerContext*)in_ctx; + + Argon2Block scratch @noinit; + Argon2Block index_block @noinit; + uint index_ctr = 1; + Argon2Block* segment_start = &ctx.blocks[ctx.lane * ctx.cols + ctx.slice_offset]; + + for (uint block = ctx.pass_offset; block < ctx.blocks_per_segment; block++) + { + Argon2Block* current = segment_start + block; + Argon2Block* previous = (!block && !ctx.slice_offset) ? (segment_start + ctx.cols - 1) : (segment_start + block - 1); + + ulong index_seed; + if (!ctx.use_constant_time) + { + index_seed = previous.q[0]; + } + else + { + if (block == ctx.pass_offset || (block % 128) == 0) + { + mem::zero_volatile(index_block.b[..]); + index_block.q[0] = ctx.pass; + index_block.q[1] = ctx.lane; + index_block.q[2] = ctx.slice; + index_block.q[3] = ctx.num_blocks; + index_block.q[4] = ctx.opts_ref.iterations; + index_block.q[5] = ctx.opts_ref.algorithm; + index_block.q[6] = index_ctr; + index_ctr++; + + scratch.copy(&index_block); + index_block.compress().xor_with(&scratch); + + scratch.copy(&index_block); + index_block.compress().xor_with(&scratch); + } + index_seed = index_block.q[block % 128]; + } + uint next_slice = ((ctx.slice + 1u) % 4) * ctx.blocks_per_segment; + uint window_start = !ctx.pass ? 0u : next_slice; + uint nb_segments = !ctx.pass ? ctx.slice : 3u; + uint window_size = nb_segments * ctx.blocks_per_segment + block - 1u; + // Find reference block + ulong j1 = (uint)index_seed; // block selector + ulong j2 = (uint)(index_seed >> 32); // lane selector + ulong x = (j1 * j1) >> 32; + ulong y = (ulong)(window_size * x) >> 32; + ulong z = (ulong)(window_size - 1u) - y; + ulong ref = (window_start + z) % ctx.cols; + uint index = (uint)(j2 % ctx.rows) * ctx.cols + (uint)ref; + Argon2Block* reference = &ctx.blocks[index]; + + scratch.copy(previous).xor_with(reference); + if (ctx.pass == 0) { current.copy(&scratch); } else { current.xor_with(&scratch); } + scratch.compress(); + current.xor_with(&scratch); + } + + return 0; +} + +<* + Generate a resultant Argon2 hash based on the specified options. The size of the `outbuf` result slice + determines the byte-length of the generated output tag. + + Setting the feature flag "ARGON2_FORCE_SEQUENTIAL" at compile-time will force this function to avoid + the use of any native threading libraries where available. + + It should be noted that it's probably not well-advised to use the temp=allocator `tmem` when + working with this construct. You should probably just use `mem`, since this function will clean + up after itself anyway, and the caller's allocation of `outbuf` is done independently. Nevertheless, + a custom allocator can always be provided based on the operating environment. + + @param [out] outbuf : "The destination buffer for the resultant Argon2 tag. The size of this slice determines the tag's length." + @param [in] input : "The data to consume and hash." + @param options : "Chosen algorithm options for computing the resultant tag. See the `Argon2Options` struct for more information." + + @require outbuf.len >= 4 && outbuf.len < uint::max : "The tag length must be at least 4 bytes and no more than (2^32)-1" + @require input.len < uint::max : "The input data (P) length must be less than 2^32 bytes." + @require options.salt.len < uint::max : "The salt (S) length must be less than 2^32 bytes." + @require options.lanes > 0 && options.lanes < int::max : "Parallelism (p) must be greater than 0 and less than 2^31." + @require options.mem_size >= options.lanes << 3 && options.mem_size < uint::max : "Memory size (m) must be at least 8*p and less than 2^32." + @require options.iterations > 0 && options.iterations < uint::max : "Passes (t) must be greater than 0 and less than 2^32." +*> +fn void hash_raw(char[] outbuf, char[] input, Argon2Options options) => @pool() +{ + char[64 + 2 * uint::size] digest @noinit @align(uint::size); + + $if $feature(ARGON2_FORCE_SEQUENTIAL): + $echo "Argon2: forcing the use of sequential lane processing."; + $endif + + // Sec 3.2, item 1 : H_0 is a Blake2b_512 hash of a concatenation of values. + Blake2b h0 @noinit; + h0.init(blake2::SIZE_512); + h0.update(@le_c(options.lanes)); // LE32(p) + h0.update(@le_c((uint)outbuf.len)); // LE32(T) + h0.update(@le_c(options.mem_size)); // LE32(m) + h0.update(@le_c(options.iterations)); // LE32(t) + h0.update(@le_c((uint)VERSION)); // LE32(v) + h0.update(@le_c((uint)options.algorithm)); // LE32(y) + h0.update(@le_c((uint)input.len)); // LE32(lengthof P) + h0.update(input); // LE32(P) + h0.update(@le_c((uint)options.salt.len)); // LE32(lengthof S) + h0.update(options.salt); // LE32(S) + h0.update(@le_c((uint)options.key.len)); // LE32(lengthof K) + h0.update(options.key); // LE32(K) + h0.update(@le_c((uint)options.assoc.len)); // LE32(lengthof X) + h0.update(options.assoc); // LE32(X) + h0.final(digest[:64]); // implicitly wipes the Blake2b context + + // For p lanes, the memory is organized in a matrix B[i][j] of blocks with p rows (lanes) and q = m' / p columns. + uint m_prime = 4ul * options.lanes * (options.mem_size / (4ul * options.lanes)); + uint blocks_per_lane = m_prime / options.lanes; + uint blocks_per_segment = blocks_per_lane / 4; // each lane/row is divided into 4 slices, the intersection of a slice and lane is a "segment" + + Argon2Block[] blocks = alloc::alloc_array_aligned(tmem, Argon2Block, (sz)m_prime)[:m_prime]; + defer foreach (&b : blocks) mem::zero_volatile(b.b[..]); + + for (uint lane = 0; lane < options.lanes; lane++) + { + for (uint j = 0; j < 2; j++) + { + *(uint*)(&digest[64]) = @le(j); + *(uint*)(&digest[68]) = @le(lane); + h_prime(blocks[lane * blocks_per_lane + j].b[..], digest[..]); + } + } + mem::zero_volatile(digest[..]); + + // Build a template context for all threads to derive their context details from. + Argon2WorkerContext template_context = { + .opts_ref = &options, + .num_blocks = m_prime, + .rows = options.lanes, + .cols = blocks_per_lane, + .blocks_per_segment = blocks_per_segment, + .use_constant_time = options.algorithm != ARGON2_D, + .blocks = blocks, + }; + + // Allocate one recycled context per lane. For synchronous operations, this is just looped over. + // In threaded environments, each thread gets one and a `join` awaits them all to finish for the current slice. + // + // NOTE: A ThreadPool instance could have been used here, but that requires a compile-time constant to determine its size, + // and the amount of threads to start should simply match the amount of lanes. + // + Argon2WorkerContext[] contexts = alloc::new_array(tmem, Argon2WorkerContext, (sz)options.lanes); + Thread[] workers = alloc::new_array(tmem, Thread, (sz)options.lanes); + + for (uint pass = 0; pass < options.iterations; pass++) + { + template_context.pass = pass; + for (uint slice = 0; slice < 4; slice++) + { + template_context.slice = slice; + template_context.pass_offset = (pass == 0 && slice == 0) ? 2 : 0; + template_context.slice_offset = slice * blocks_per_segment; + + if (slice == 2 && options.algorithm == ARGON2_ID) template_context.use_constant_time = false; + + // Initialize each lane's processing context for this slice. + foreach (uint i, &context : contexts) + { + *context = { ...template_context, .lane = i, }; + + $if USE_THREADING: + workers[i].create(&process_lane_slice, context)!!; + $else + process_lane_slice(context); + $endif + } + + $if USE_THREADING: // all threads must join after all lane-slices have finished processing + foreach (w : workers) w.join()!!; + $endif + } + } + + // Starting from the second lane, XOR each block at the end of a lane with the block from the end of the preceding lane. + for (uint lane = 1; lane < options.lanes; lane++) + { + blocks[(lane + 1u) * blocks_per_lane - 1u].xor_with(&blocks[lane * blocks_per_lane - 1u]); + } + + Argon2Block scratch @noinit; + defer mem::zero_volatile(scratch.b[..]); + + scratch.copy(&blocks[^1]); + h_prime(outbuf, scratch.b[..]); +} + + +<* + Encode a hash result and an `Argon2Options` struct into a PHC String Format output. + See: https://github.com/P-H-C/phc-string-format/blob/e8fbd333dcc9a8b0843fac6b33371cf157e91a48/phc-sf-spec.md + + @param [&inout] allocator : "Which allocator to use for building the output string." + @param [in] hash_value : "The Argon2 output hash as a raw byte array. This function handles Base64 encoding on its own." + @param options : "The options which were used upon creating the `hash_value`." + @param [in] key_id : "An OPTIONAL byte array representing the ID of the pre-shared secret key to use. This is application-dependent and therefore not required." + + @require hash_value.len > 0 : "The `hash_value` input must have a length greater than zero." +*> +fn String encode(Allocator allocator, char[] hash_value, Argon2Options options, char[] key_id = {}) => @pool() +{ + String encoded_hash = base64::tencode(hash_value, padding: base64::NO_PAD); + String encoded_salt = options.salt.len ? base64::tencode(options.salt, padding: base64::NO_PAD) : {}; + String encoded_key_id = key_id.len ? base64::tencode(key_id, padding: base64::NO_PAD) : {}; + String encoded_assoc = options.assoc.len ? base64::tencode(options.assoc, padding: base64::NO_PAD) : {}; + + // This could have been the encoded lengths plus a few hundred, but it never hurts to be more explicit. + sz min_size = + 9 // up to "$argon2id" + + 6 // "$v=19$" + + 13 // "m=111," - up to 10 decimal integers assumed possible, per spec + + 13 // same for "t=" + + 13 // we just assume this is also 10 digits possible, despite the spec saying "up to 3 digits" + + 7 // ",keyid=" + + 6 // ",data=" + + 3 // extra "$" separators + + encoded_hash.len + encoded_salt.len + encoded_key_id.len + encoded_assoc.len + + 128; // extra padding, just in case + + // Knowing the lengths of all encoded fields, plus the general size of the other identifiers, + // we should be able to form a safe capacity to use for the dynamic string. + DString outstr; + outstr.tinit(capacity: min_size); + outstr.append("$argon2"); + switch (options.algorithm) + { + case ARGON2_D: outstr.append("d"); + case ARGON2_I: outstr.append("i"); + case ARGON2_ID: outstr.append("id"); + } + outstr.append("$v=19$"); // this implementation only supports V = 0x13, according to spec, no version 0x10 + + // The three parameters m,t,p MUST appear in that order and are REQUIRED. keyid and data are optional. + outstr.appendf("m=%d,", options.mem_size); + outstr.appendf("t=%d,", options.iterations); + outstr.appendf("p=%d", options.lanes); + + // While the spec says 0 to 8 bytes, we let the user determine their own use-case for this field, and assume that + // options.key already contains an encoded form of whatever Key ID they might be using. + if (encoded_key_id.len) outstr.appendf(",keyid=%s", encoded_key_id); + // Assumptions carry over for associated data too. + if (encoded_assoc.len) outstr.appendf(",data=%s", encoded_assoc); + + // Remember: a salt is an _optional_ value here. + if (encoded_salt.len) outstr.appendf("$%s", encoded_salt); + + // Lastly, append the hash Base64. + outstr.appendf("$%s", encoded_hash); + + // Done! Return a copy to self-contain all other allocations. + return outstr.copy_str(allocator); +} + +<* A simple alias of `encode` implicitly using the temp allocator. *> +macro tencode(char[] hash_value, Argon2Options options, char key_id = {}) + => encode(tmem, hash_value, options, key_id: key_id); + + +<* + Perform an Argon2 hashing operation and encode its result as a PHC-SF string in one go. + + @param [&inout] allocator : "Which allocator to use for building the output string." + @param [in] input : "The data to consume and hash." + @param options : "Chosen algorithm options for computing the resultant tag. See the `Argon2Options` struct for more information." + @param [in] key_id : "An identifier to use for an unconveyed private key." + @param out_size : "The output size of the intermediate hash value. This is 32 bytes by default." + + @require out_size >= 4 && out_size < sz::max : "The hash's output size must be >= 4 bytes but less than sz::max." +*> +fn String hash(Allocator allocator, char[] input, Argon2Options options, char[] key_id = {}, sz out_size = 32) => @pool() +{ + char[] result = alloc::alloc_array(tmem, char, out_size); + hash_raw(result[..], input, options); + return encode(allocator, result[..], options, key_id: key_id); +} + +<* A simple alias of `hash` implicitly using the temp allocator. *> +macro thash(char[] input, Argon2Options options, char[] key_id = {}, sz out_size = 32) + => hash(tmem, input, options, key_id: key_id, out_size: out_size); + + +<* Hash parameter key names must only use `[a-z0-9-]` characters. *> +const AsciiCharset PARAMETER_NAMES_SET = ascii::@combine_sets(ascii::ALPHA_LOWER_SET, ascii::NUMBER_SET, ascii::@create_set("-")); + +<* Hash parameter values must only use `[A-Za-z0-9/+.-]` characters. *> +const AsciiCharset PARAMETER_VALUES_SET = ascii::@combine_sets(ascii::ALPHANUMERIC_SET, ascii::@create_set("/+.-")); + +<* Used to determine which options were already 'seen'. *> +bitstruct SeenOptions : char @local +{ + bool m : 0; + bool t : 1; + bool p : 2; + bool key_id : 3; + bool assoc : 4; + char _unused : 5..7; +} + +<* Represents a set of decoded information from an incoming Argon2 hash string. *> +struct Argon2Decoded (Printable) +{ + <* The decoded Argon2 options destructured from the string. *> + inline Argon2Options opts; + <* The decoded hash value as a byte-array. *> + char[] hash; + <* An application-specific key identifier conveyed with the hash string's options. *> + char[] key_id; + <* The version value of the hash. This implementation currently only supports 0x13, but this must be parsed nonetheless. *> + char version; + <* Handle to the allocator used to allocate the decoded Base64 values. Only intended to be used with `self.free` calls. *> + Allocator allocator; +} + +<* Implement `Printable` for deserialized Argon2 information. *> +fn sz? Argon2Decoded.to_format(&self, Formatter* f) @dynamic +{ + return f.printf( + "hash=%h%s,key_id=%h%s,v=%d,opts=%s", + self.hash[:min(self.hash.len, 32)], self.hash.len > 32 ? "..." : "", + self.key_id[:min(self.key_id.len, 8)], self.key_id.len > 8 ? "..." : "", + self.version, + self.opts + ); +} + +<* Allows comparisons between two decoded Argon2 hash strings. *> +fn bool Argon2Decoded.eq(&self, Argon2Decoded other) @operator(==) +{ + // NOTE: do not compare allocators (obviously). + return self.opts == other.opts && self.hash == other.hash && self.key_id == other.key_id && self.version == other.version; +} + +<* Release any memory acquired by duplicating Argon2 options into this decoded structure, then zeroize. *> +fn void Argon2Decoded.free(&self) +{ + if (self.allocator == tmem) return; // no need to do this with the temp allocator + alloc::free(self.allocator, self.hash); + alloc::free(self.allocator, self.salt); + alloc::free(self.allocator, self.key_id); + alloc::free(self.allocator, self.assoc); + + mem::zero_volatile(@as_char_view(*self)); +} + +<* Various descriptive faults must be defined for decoding incoming PHC String Format values. *> +faultdef + BAD_PARAMETER_KEY, + BAD_PARAMETER_VALUE, + DUPLICATE_PARAMETERS, + INVALID_ALGORITHM, + INVALID_BASE64, + INVALID_FORMAT, + INVALID_ASSOC, + INVALID_HASH_ENCODING, + INVALID_ITERATIONS, + INVALID_KEY_ID, + INVALID_MEM_SIZE, + INVALID_PARALLELISM, + INVALID_SALT_ENCODING, + INVALID_VERSION, + NOT_ENOUGH_PARAMETERS, + MISSING_ITERATIONS, + MISSING_MEM_SIZE, + MISSING_PARALLELISM; + +<* Helper macro for marking a parameter as seen or throwing if it was already seen. *> +macro @saw(#field) @local +{ + if (#field == true) return DUPLICATE_PARAMETERS~; + #field = true; +} + + +<* +*> +fn void? _parse_option(Argon2Decoded* result, SeenOptions* seen, String token, char[]* decoded_key_id, char[]* decoded_assoc) @local +{ + if (token.len < 3) return INVALID_FORMAT~; + + String[] key_val = token.tsplit("=", max: 2, skip_empty: false); + + String key = key_val[0]; + String value = key_val[1]; + + if (!key.len || !value.len) return INVALID_FORMAT~; + + // No need to validate `key` here since the switch-case below does that for us already. + foreach (c : value) if (!PARAMETER_VALUES_SET.contains(c)) return BAD_PARAMETER_VALUE~; + + // Technically we should be asserting the order of the keys as `m,t,p,keyid,data`, but as long as all data + // here is being conveyed, there's not a strong reason why we shouldn't just parse it anyway. + switch (key) + { + case "m": + @saw(seen.m)!; + foreach (c : value) if (!ascii::NUMBER_SET.contains(c)) return INVALID_MEM_SIZE~; + result.mem_size = (value.to_uint() ?? INVALID_MEM_SIZE~)!; + if (result.mem_size < 1 || result.mem_size == uint::max) return INVALID_MEM_SIZE~; + case "t": + @saw(seen.t)!; + foreach (c : value) if (!ascii::NUMBER_SET.contains(c)) return INVALID_ITERATIONS~; + result.iterations = (value.to_uint() ?? INVALID_ITERATIONS~)!; + if (result.iterations < 1 || result.iterations == uint::max) return INVALID_ITERATIONS~; + case "p": + @saw(seen.p)!; + foreach (c : value) if (!ascii::NUMBER_SET.contains(c)) return INVALID_PARALLELISM~; + result.lanes = (value.to_uint() ?? INVALID_PARALLELISM~)!; + if (result.lanes < 1 || result.lanes > char::max) return INVALID_PARALLELISM~; + case "keyid": + @saw(seen.key_id)!; + *decoded_key_id = base64::tdecode(value, padding: base64::NO_PAD) ?? INVALID_KEY_ID~!; + case "data": + @saw(seen.assoc)!; + *decoded_assoc = base64::tdecode(value, padding: base64::NO_PAD) ?? INVALID_ASSOC~!; + default: return BAD_PARAMETER_KEY~; + } +} + +<* + Decode a PHC String Format hash, specifically for Argon2, into usable values. + + @param [in] hash_str : "The PHC-SF hash string to decipher." + + @require hash_str.len > 0 : "The hash string cannot be empty." +*> +fn Argon2Decoded? decode(Allocator allocator, String hash_str) => @pool() +{ + Argon2Decoded result; + char[] decoded_hash; + char[] decoded_salt; + char[] decoded_key_id; + char[] decoded_assoc; + + if (hash_str[0] != '$') return INVALID_FORMAT~; + hash_str = hash_str[1..]; + + String[] outer_tokens = hash_str.tsplit("$", max: 5, skip_empty: false); + + if (outer_tokens.len < 4) return INVALID_FORMAT~; // there must be a minimum of 4 different tokens, up to 5 + foreach (token : outer_tokens) if (token.len < 3) return INVALID_FORMAT~; + + // The first three tokens must always be the same: algorithm, version, parameters/options + switch (outer_tokens[0]) + { + case "argon2d": result.opts.algorithm = ARGON2_D; + case "argon2i": result.opts.algorithm = ARGON2_I; + case "argon2id": result.opts.algorithm = ARGON2_ID; + default: return INVALID_ALGORITHM~; + } + + if (outer_tokens[1][:2] != "v=") return INVALID_VERSION~; + foreach (c : outer_tokens[1][2..]) if (!ascii::NUMBER_SET.contains(c)) return INVALID_VERSION~; + result.version = (outer_tokens[1][2..].to_char() ?? INVALID_VERSION~)!; + + String[] option_tokens = outer_tokens[2].tsplit(",", max: 5, skip_empty: false); + + if (option_tokens.len < 3) return NOT_ENOUGH_PARAMETERS~; + + SeenOptions seen; + foreach (token : option_tokens) _parse_option(&result, &seen, token, &decoded_key_id, &decoded_assoc)!; + + if (!seen.m) return MISSING_MEM_SIZE~; + if (!seen.t) return MISSING_ITERATIONS~; + if (!seen.p) return MISSING_PARALLELISM~; + + // The fourth token can be either the salt or the hash value. + sz index = 3; + if (outer_tokens.len > 4) + { + decoded_salt = base64::tdecode(outer_tokens[index], padding: base64::NO_PAD) ?? INVALID_SALT_ENCODING~!; + index++; + } + + // The final token is always the actual resultant hash value. + decoded_hash = base64::tdecode(outer_tokens[index], padding: base64::NO_PAD) ?? INVALID_HASH_ENCODING~!; + + // Link the passed allocator to the result object, so it can be freed correctly later with `.free()`. + result = { + ...result, + .allocator = allocator, + .hash = alloc::clone_slice(allocator, decoded_hash), + .salt = alloc::clone_slice(allocator, decoded_salt), + .key_id = alloc::clone_slice(allocator, decoded_key_id), + .assoc = alloc::clone_slice(allocator, decoded_assoc), + }; + return result; +} + +<* A simple alias of `decode` implicitly using the temp allocator. *> +macro tdecode(String hash_str) => decode(tmem, hash_str); + + +<* + Given a hash string and a password, verify that the password is correct. + + Note that this function SHOULD NOT use the `tmem` temp allocator if the `mem_size` of + the hash is expected to be larger than the maximum pool size. + + @param [&inout] allocator : "The allocator to use. This function should never leak." + @param [in] hash_str : "The PHC-SF hash string to decipher." + @param [in] password : "The password value to use when attempting to reconstruct `hash_str`." + @param [in] key : "A secret key that's added to the hash value. This cannot legally be conveyed by the hash string." + @param $rethrows : "When `false`, removes the optional return from the function." +*> +macro verify(Allocator allocator, String hash_str, char[] password, char[] key = {}, bool $rethrows = true) => @pool() +{ + $if $rethrows: + Argon2Decoded c = tdecode(hash_str)!; + $else + Argon2Decoded c; + if (catch @try(c, tdecode(hash_str))) return false; + $endif + c.key = key; + return hash_str == thash(password, c.opts, out_size: c.hash.len); +} + +<* A simple alias of `verify` implicitly using the temp allocator. *> +macro tverify(String hash_str, char[] password, char[] key = {}, bool $rethrows = true) + => verify(tmem, hash_str, password, key: key, $rethrows: $rethrows); diff --git a/test/stdlib/src/crypto/chacha20.c3 b/test/stdlib/src/crypto/chacha20.c3 index 50984bf..2aeaffa 100644 --- a/test/stdlib/src/crypto/chacha20.c3 +++ b/test/stdlib/src/crypto/chacha20.c3 @@ -39,11 +39,11 @@ bool permit_overflow = false; struct ChaCha20 { <* The position within a block before permuting the rounds. *> - usz position; + sz position; <* Count of bytes processed. Useful to track an approach to the 256GiB limit of a single context. *> - ulong bytes_processed; + long bytes_processed; <* The key stream or state used during cipher block operations. *> - uint[16] key_stream @align(ulong.sizeof); + uint[16] key_stream @align(ulong::size); <* The secret key for the context. *> char[32] key; <* The one-time nonce (or IV - initialization vector) used for the context. *> @@ -67,7 +67,7 @@ fn void chacha20_mutate_keystream(ChaCha20* self) @local @inline { self.key_stream[..] = self.state[..]; - for (usz i = 0; i < 10; i++) // unrolling this does not improve performance measurably + for (sz i = 0; i < 10; i++) // unrolling this does not improve performance measurably { quarter_round(&self.key_stream[0], 0, 4, 8, 12); quarter_round(&self.key_stream[0], 1, 5, 9, 13); @@ -118,14 +118,14 @@ fn void ChaCha20.transform(&self, char[] data) { if (!data.len) return; - usz original_length = data.len; + sz original_length = data.len; char[] key_stream = @as_char_view(self.key_stream); // 1. Process remaining bytes in the current keystream block. if (self.position < BLOCK_SIZE) { - usz len = data.len < (BLOCK_SIZE - self.position) ? data.len : (BLOCK_SIZE - self.position); - for (usz i = 0; i < len; i++) data[i] ^= key_stream[self.position + i]; + sz len = data.len < (BLOCK_SIZE - self.position) ? data.len : (BLOCK_SIZE - self.position); + for (sz i = 0; i < len; i++) data[i] ^= key_stream[self.position + i]; self.position += len; data = data[len..]; } @@ -133,16 +133,16 @@ fn void ChaCha20.transform(&self, char[] data) // 2. Get the amount of bytes offset from the nearest alignment boundary. // Process full blocks at a time, word by word according to the system's architecture. // Any extra bytes on each side are dynamically processed byte-by-byte. - usz offset = usz.sizeof - (((usz)data.ptr % usz.sizeof) ?: usz.sizeof); + sz offset = sz::size - (((sz)data.ptr % sz::size) ?: sz::size); - for (usz x = offset; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..], x = offset) + for (sz x = offset; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..], x = offset) { chacha20_mutate_keystream(self); if (offset) foreach (i, &b : data[:offset]) *b ^= key_stream[i]; char[] aligned_data = data[offset..]; - for (; x <= (BLOCK_SIZE - usz.sizeof); x += usz.sizeof) + for (; x <= (BLOCK_SIZE - sz::size); x += sz::size) { - ((usz*)aligned_data.ptr)[x / usz.sizeof] ^= mem::load((usz*)(&key_stream[x]), $align: 1); + ((sz*)aligned_data.ptr)[x / sz::size] ^= mem::load((sz*)(&key_stream[x]), $align: 1); } for (; x < BLOCK_SIZE; x++) data[x] ^= key_stream[x]; } @@ -151,7 +151,7 @@ fn void ChaCha20.transform(&self, char[] data) if (data.len > 0) { chacha20_mutate_keystream(self); - for (usz i = 0; i < data.len; i++) data[i] ^= key_stream[i]; + for (sz i = 0; i < data.len; i++) data[i] ^= key_stream[i]; self.position = data.len; } @@ -207,7 +207,7 @@ alias decrypt_mut = crypt; fn char[] crypt_clone(Allocator allocator, char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private { if (@unlikely(!data.len)) return {}; - char[] buff = allocator::clone_slice(allocator, data); + char[] buff = alloc::clone_slice(allocator, data); crypt(buff, key, nonce, counter); return buff; } diff --git a/test/stdlib/src/crypto/crypto.c3 b/test/stdlib/src/crypto/crypto.c3 index f9758de..57e7cad 100644 --- a/test/stdlib/src/crypto/crypto.c3 +++ b/test/stdlib/src/crypto/crypto.c3 @@ -1,9 +1,9 @@ module std::crypto; -fn bool safe_compare(void* data1, void* data2, usz len) +fn bool safe_compare(void* data1, void* data2, sz len) { char match = 0; - for (usz i = 0; i < len; i++) + for (sz i = 0; i < len; i++) { match = match | (mem::@volatile_load(((char*)data1)[i]) ^ mem::@volatile_load(((char*)data2)[i])); } diff --git a/test/stdlib/src/crypto/ed25519.c3 b/test/stdlib/src/crypto/ed25519.c3 index 099d9ea..3c2d0c0 100644 --- a/test/stdlib/src/crypto/ed25519.c3 +++ b/test/stdlib/src/crypto/ed25519.c3 @@ -6,18 +6,18 @@ module std::crypto::ed25519; import std::hash::sha512; alias Ed25519PrivateKey = char[32]; -alias Ed25519PublicKey = char[Ed25519PrivateKey.len]; -alias Ed25519Signature = char[2 * Ed25519PublicKey.len]; +alias Ed25519PublicKey = char[Ed25519PrivateKey::len]; +alias Ed25519Signature = char[2 * Ed25519PublicKey::len]; <* Generate a public key from a private key. @param [in] private_key : "32 bytes of cryptographically secure random data" - @require private_key.len == Ed25519PrivateKey.len + @require private_key.len == Ed25519PrivateKey::len *> fn Ed25519PublicKey public_keygen(char[] private_key) { - return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt.len]))); + return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt::len]))); } <* @@ -26,8 +26,8 @@ fn Ed25519PublicKey public_keygen(char[] private_key) @param [in] message @param [in] private_key @param [in] public_key - @require private_key.len == Ed25519PrivateKey.len - @require public_key.len == Ed25519PublicKey.len + @require private_key.len == Ed25519PrivateKey::len + @require public_key.len == Ed25519PublicKey::len *> fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key) { @@ -38,23 +38,23 @@ fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key) Sha512 sha @noinit; sha.init(); - sha.update(exp[FBaseInt.len..]); + sha.update(exp[FBaseInt::len..]); sha.update(message); FBaseInt k = from_bytes(&&sha.final()); - r[:F25519Int.len] = pack(&&unproject(&&(BASE * k[..])))[..]; + r[:F25519Int::len] = pack(&&unproject(&&(BASE * k[..])))[..]; sha.init(); - sha.update(r[:F25519Int.len]); + sha.update(r[:F25519Int::len]); sha.update(public_key); sha.update(message); FBaseInt z = from_bytes(&&sha.final()); - FBaseInt e = from_bytes(exp[:FBaseInt.len]); + FBaseInt e = from_bytes(exp[:FBaseInt::len]); - r[F25519Int.len..] = (z * e + k)[..]; + r[F25519Int::len..] = (z * e + k)[..]; return r; } @@ -65,14 +65,14 @@ fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key) @param [in] message @param [in] signature @param [in] public_key - @require signature.len == Ed25519Signature.len - @require public_key.len == Ed25519PublicKey.len + @require signature.len == Ed25519Signature::len + @require public_key.len == Ed25519PublicKey::len *> fn bool verify(char[] message, char[] signature, char[] public_key) { char ok = 1; - F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int.len..]))); + F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int::len..]))); Unpacking unp_p = unpack_on_curve((F25519Int*)public_key); Projection p = project(&unp_p.point); @@ -81,7 +81,7 @@ fn bool verify(char[] message, char[] signature, char[] public_key) Sha512 sha @noinit; sha.init(); - sha.update(signature[:F25519Int.len]); + sha.update(signature[:F25519Int::len]); sha.update(public_key); sha.update(message); @@ -89,7 +89,7 @@ fn bool verify(char[] message, char[] signature, char[] public_key) p = p * z[..]; - Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int.len]); + Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int::len]); Projection q = project(&unp_q.point); ok &= unp_q.on_curve; @@ -113,15 +113,15 @@ const Projection BASE @private = Compute the pruned SHA-512 hash of a private key. @param [in] private_key - @require private_key.len == Ed25519PrivateKey.len + @require private_key.len == Ed25519PrivateKey::len *> fn char[sha512::HASH_SIZE] expand_private_key(char[] private_key) @local { char[*] r = sha512::hash(private_key); r[0] &= 0b11111000; - r[FBaseInt.len - 1] &= 0b01111111; - r[FBaseInt.len - 1] |= 0b01000000; + r[FBaseInt::len - 1] &= 0b01111111; + r[FBaseInt::len - 1] |= 0b01000000; return r; } @@ -204,7 +204,7 @@ fn F25519Int pack(Point* p) r.x.normalize(); r.y.normalize(); - r.y[^1] |= (r.x[0] & 1) << 7; + r.y[^1] |= (r.x[0] & 1u) << 7; return r.y; } @@ -235,7 +235,7 @@ fn Unpacking unpack_on_curve(F25519Int* encoding) F25519Int x = x2.sqrt(); - p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1); + p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1u); F25519Int _x2 = p.x * p.x; @@ -308,13 +308,13 @@ fn Projection Projection.mul(&s, char[] n) @operator(*) { Projection r = NEUTRAL; - for (isz i = n.len << 3 - 1; i >= 0; i--) + for (sz i = n.len << 3 - 1; i >= 0; i--) { r = r.twice(); Projection t = r + s; - char bit = n[i >> 3] >> (i & 7) & 1; + char bit = n[i >> 3] >> (i & 7) & 1u; r.x = f25519_select(&r.x, &t.x, bit); r.y = f25519_select(&r.y, &t.y, bit); r.z = f25519_select(&r.z, &t.z, bit); @@ -396,7 +396,7 @@ fn char eq(F25519Int* a, F25519Int* b) e |= (e >> 2); e |= (e >> 1); - return e ^ 1; + return e ^ 1u; } <* @@ -505,13 +505,13 @@ fn F25519Int F25519Int.mul(&s, F25519Int* n) @operator(*) F25519Int r @noinit; uint c; - for (usz i = 0; i < F25519Int.len; i++) + foreach (i, &ref : r) { c >>= 8; - for (usz j; j <= i; j++) c += (*s)[j] * (*n)[i - j]; + for (sz j; j <= i; j++) c += (*s)[j] * (*n)[i - j]; // Reduce using 2^256 = 2*19 mod p - for (usz j = i + 1; j < F25519Int.len; j++) c += (*s)[j] * (*n)[^j - i] * 2 * 19; - r[i] = (char)c; + for (sz j = i + 1; j < F25519Int::len; j++) c += (*s)[j] * (*n)[^j - i] * 2u * 19u; + *ref = (char)c; } r.reduce_carry(c >> 7); @@ -551,7 +551,7 @@ fn F25519Int F25519Int.inv(&s) //Compute s^(p-2) F25519Int r = *s; - for (usz i; i < 255 - 1 - 5; i++) r = r * r * s; + for (sz i; i < 255 - 1 - 5; i++) r = r * r * s; r *= r; r = r * r * s; @@ -571,7 +571,7 @@ fn F25519Int pow_2523(F25519Int* s) @local { F25519Int r = *s; - for (usz i; i < 252 - 1 - 2; i++) r = r * r * s; + for (sz i; i < 252 - 1 - 2; i++) r = r * r * s; r *= r; r = r * r * s; @@ -615,10 +615,10 @@ fn FBaseInt from_bytes(char[] bytes) { FBaseInt r; - usz bitc = min(252 - 1, bytes.len << 3); - usz bytec = bitc >> 3; - usz mod = bitc & 7; - usz rem = bytes.len << 3 - bitc; + sz bitc = min(252 - 1, bytes.len << 3); + sz bytec = bitc >> 3; + sz mod = bitc & 7; + sz rem = bytes.len << 3 - bitc; r[:bytec] = bytes[^bytec..]; @@ -628,10 +628,10 @@ fn FBaseInt from_bytes(char[] bytes) r[0] |= bytes[^bytec + 1] >> (8 - mod); } - for (isz i = rem - 1; i >= 0; i--) + for (sz i = rem - 1; i >= 0; i--) { r <<= 1; - r[0] |= bytes[i >> 3] >> (i & 7) & 1; + r[0] |= bytes[i >> 3] >> (i & 7u) & 1u; r = r.sub_l(&ORDER); } @@ -690,7 +690,7 @@ fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n) { c = v - (*n)[i] - c; sub[i] = (char)c; - c = (c >> 8) & 1; + c = (c >> 8) & 1u; } return fbase_select(&sub, s, (char)c); @@ -701,7 +701,7 @@ fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n) @param [&in] s *> -fn FBaseInt FBaseInt.shl(&s, usz n) @operator(<<) +fn FBaseInt FBaseInt.shl(&s, sz n) @operator(<<) { FBaseInt r @noinit; @@ -727,10 +727,10 @@ fn FBaseInt FBaseInt.mul(&s, FBaseInt* n) @operator(*) { FBaseInt r; - for (isz i = 252; i >= 0; i--) + for (sz i = 252; i >= 0; i--) { r = (r << 1).sub_l(&ORDER); - r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7) & 1); + r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7u) & 1u); } return r; diff --git a/test/stdlib/src/crypto/entropy.c3 b/test/stdlib/src/crypto/entropy.c3 new file mode 100644 index 0000000..a24be3a --- /dev/null +++ b/test/stdlib/src/crypto/entropy.c3 @@ -0,0 +1,148 @@ +// Copyright (c) 2026 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// Based on an initial version by @laura240406 on GitHub. See `c3c` PR #2812. +// +module std::crypto::entropy; +import libc; + +faultdef SECURE_RANDOM_UNAVAILABLE; + +<* + Returns cryptographically secure random bytes. Fails if it is unavailable. + + @param [out] buffer : "The buffer to populate with random bytes." + @param timeout : "Set to `true` to disallow blocking when this function cannot fully fill the provided `buffer`." + + @return? SECURE_RANDOM_UNAVAILABLE : "The delegated RNG wasn't available or failed to fully populate the output buffer." + @return? TIMEOUT : "When `timeout` is `true`, the secure RNG failed to populate the buffer in a reasonable time." +*> +fn void? fill(char[] buffer, bool timeout = false) +{ + if (!buffer.len) return; + $switch: + $case env::LINUX: + $case env::ANDROID: + $case env::FREEBSD: + $case env::NETBSD: + $if (@in(env::ARCH_TYPE, ARM, ARMB, AARCH64, X86, X86_64)): + sz result = (sz)$$syscall(entropy_syscall_id(), (uptr)buffer.ptr, (uptr)buffer.len, 0); + if (result < 0 && result != Errno.EINTR) return SECURE_RANDOM_UNAVAILABLE~; /* it's possible for signal handlers to interrupt this call */ + for (sz prevent_block = 0; result < buffer.len && (!timeout || prevent_block < 1_000); prevent_block++) + { + sz more = (sz)$$syscall(entropy_syscall_id(), (uptr)(&buffer[result]), (uptr)(buffer.len - result), 0); + if (more < 0) + { + if (more != Errno.EINTR) return SECURE_RANDOM_UNAVAILABLE~; + continue; + } + result += more; + } + if (result < buffer.len) return TIMEOUT~; + $else + return unix_fallback(buffer, timeout); + $endif + $case env::OPENBSD: + $case env::DARWIN: + arc4random_buf((void*)buffer.ptr, buffer.len); + return; + $case env::WIN32: + win32_init(); + if (rtlgenrandom == null || !rtlgenrandom(buffer.ptr, (ulong)buffer.len)) return SECURE_RANDOM_UNAVAILABLE~; + $default: + return SECURE_RANDOM_UNAVAILABLE~; + $endswitch +} + +<* + Create a new byte array and fill it with random bytes from a secure, high-entropy source. + + @param [&inout] allocator : "The allocator to use when creating the byte array." + @param length : "The length of the resultant byte array." + @param timeout : "Set to `true` to disallow blocking when this function cannot fully fill the provided `buffer`." + + @return? SECURE_RANDOM_UNAVAILABLE : "The delegated RNG wasn't available or failed to fully populate the output buffer." + @return? TIMEOUT : "When `timeout` is `true`, the secure RNG failed to populate the buffer in a reasonable time." + @return "The created and populated slice of random bytes." +*> +fn char[]? make(Allocator allocator, sz length, bool timeout = false) +{ + if (!length) return {}; + char[] res = alloc::alloc_array(allocator, char, length)[:length]; + fill(res, timeout)!; + return res; +} + + +module std::crypto::entropy @private @if (env::WIN32); +import std::os::win32, std::thread; + +OnceFlag run_once; +RtlGenRandom rtlgenrandom; + +alias RtlGenRandom = fn CInt(void*, ulong); + +fn void _crypto_init_win32_random() +{ + Win32_HMODULE advapi32_dll = win32::loadLibraryA("advapi32.dll"); + rtlgenrandom = win32::getProcAddress(advapi32_dll, "SystemFunction036"); +} + +macro void win32_init() => run_once.call(&_crypto_init_win32_random); + + +// ============================================================================ +module std::crypto::entropy @private; +import std::io::file; + + +<* + For relevant systems, get the syscall ID associated with `getrandom` for the platform/architecture. + + See: https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#cross-arch-numbers +*> +macro sz entropy_syscall_id() @const +{ + $switch: + $case env::LINUX: + $case env::ANDROID: + $switch: + $case env::X86_64: return 318; + $case env::ARCH_TYPE == ARM || env::ARCH_TYPE == ARMB: return 384; + $case env::AARCH64: return 278; + $case env::X86: return 355; + $endswitch + // $case env::OPENBSD: return 7; // https://github.com/openbsd/src/blob/a944417af3ca44cd189efef88c2299690e2958fd/sys/sys/syscall.h#L29 + $case env::FREEBSD: return 563; // https://github.com/freebsd/freebsd-src/blob/ff2c98b30b57b9763e2a6575f729bab676e6c025/sys/sys/syscall.h#L504 + $case env::NETBSD: return 91; // https://github.com/NetBSD/src/blob/51f0c24a1280db0c2e0b5391594c7782d8fd07e8/sys/sys/syscall.h#L275 + $default: $error "No entropy-providing syscall identifier exists for the current environment."; + $endswitch +} + +<* + A generic, UNIX-style fallback method that simply tries to access /dev/[u]random as an entropy source. + + @param buffer : "The buffer to populate with random bytes." + @param timeout : "Set to `true` to disallow blocking when this function cannot fully fill the provided `buffer`." +*> +fn void? unix_fallback(char[] buffer, bool timeout = false) +{ + File rnd_file = file::open("/dev/urandom", "r") ?? file::open("/dev/random", "r") ?? SECURE_RANDOM_UNAVAILABLE~!; + defer (void)rnd_file.close(); + sz count; + for (sz prevent_block = 0; count < buffer.len && (!timeout || prevent_block < 1_000); prevent_block++) + { + count += rnd_file.read(buffer) ?? SECURE_RANDOM_UNAVAILABLE~!; + } + if (count < buffer.len) return TIMEOUT~; +} + +<* + Import for OpenBSD which fills an existing buffer with high-entropy data. + See: https://man.openbsd.org/arc4random.3 +*> +extern fn void arc4random_buf(void* buf, sz nbytes) @if (env::OPENBSD || env::DARWIN); + + +// ============================================================================ diff --git a/test/stdlib/src/crypto/keccak/cshake.c3 b/test/stdlib/src/crypto/keccak/cshake.c3 index 5c7f1f0..ca32ac2 100644 --- a/test/stdlib/src/crypto/keccak/cshake.c3 +++ b/test/stdlib/src/crypto/keccak/cshake.c3 @@ -74,7 +74,7 @@ macro void xof_into($security_level, char[] into, char[] data, String customizer @require @in(SECURITY_LEVEL, ...USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct CShake +struct CShake @mustinit { inline Shake{SECURITY_LEVEL} under; } @@ -90,7 +90,7 @@ struct CShake fn void CShake.init(&self, NistFunctionName function = NONE, String customizer = "", char runtime_rounds = DEFAULT_ROUNDS, char runtime_delimiter = DEFAULT_DELIMITER) { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); String function_name = function.customizer; if (!customizer && !function_name) diff --git a/test/stdlib/src/crypto/keccak/keccak.c3 b/test/stdlib/src/crypto/keccak/keccak.c3 index ae093db..b8551f3 100644 --- a/test/stdlib/src/crypto/keccak/keccak.c3 +++ b/test/stdlib/src/crypto/keccak/keccak.c3 @@ -11,7 +11,7 @@ const DEFAULT_ROUNDS = 24; const DEFAULT_DELIMITER = 0x01; <* The set of valid Keccak security levels supported by this implementation. *> -const usz[] SECURITY_LEVELS = { 224, 256, 384, 512 }; +const sz[] SECURITY_LEVELS = { 224, 256, 384, 512 }; <* Get the constant Keccak `rate` value based on the given `F` and `SECURITY_LEVEL` attributes. @@ -32,7 +32,7 @@ macro rate($security_level, $f = DEFAULT_F) @const => ($f - ($security_level * 2 @param $rounds : "An optional compile-time override of the rounds count used in hash computation." @param $delimiter : "An optional compile-time override of the delimiter used in hash computation." - @require @in($security_level, ...SECURITY_LEVELS) : "Shake and Shake-derived structures must use a valid security level." + @require @in($security_level, ...SECURITY_LEVELS) : "Keccak and Keccak-derived structures must use a valid security level." @require $outlen_bytes > 0 : "You must use an output length over zero bytes." @return "A character array of the specified `$outlen_bytes` size containing the XOF result." @@ -54,7 +54,7 @@ alias hash = xof; @param [out] into : "The destination for the generated variable-length hash." @param [in] data : "The data to hash." - @require @in($security_level, ...SECURITY_LEVELS) : "Shake and Shake-derived structures must use a valid security level." + @require @in($security_level, ...SECURITY_LEVELS) : "Keccak and Keccak-derived structures must use a valid security level." @require into.len > 0 : "The destination slice must have a length greater than zero." @param $rounds : "An optional compile-time override of the rounds count used in hash computation." @param $delimiter : "An optional compile-time override of the delimiter used in hash computation." @@ -70,7 +70,11 @@ macro void xof_into($security_level, char[] into, char[] data, $rounds = DEFAULT <* When hashing with the base Keccak context, it's the same thing as using the XOF construct. *> alias hash_into = xof_into; -<* The default Keccak context always employs the default `F` value of `1600`. *> +<* + The default Keccak context always employs the default `F` value of `1600`. + + @require @in(SECURITY_LEVEL, 128, 192, ...SECURITY_LEVELS) : "Keccak and Keccak-derived structures must use a valid security level." +*> alias Keccak = KeccakContext { DEFAULT_F, SECURITY_LEVEL }; @@ -90,7 +94,7 @@ constdef KeccakState : inline int } <* The state machine is just the state, typedef'd to another type in case something else is added in the future. *> -typedef KeccakStateMachine @structlike = inline KeccakState; +typedef KeccakStateMachine = inline KeccakState; <* Initialize the runtime value of the Keccak context that indicates the _true_ size of the dependent/derived class. @@ -101,7 +105,7 @@ typedef KeccakStateMachine @structlike = inline KeccakState; @param #instance : "The `KeccakContext` instance to modify." @param runtime_size : "The struct's size in bytes." *> -macro void @init_runtime_size(#instance, usz runtime_size) +macro void @init_runtime_size(#instance, sz runtime_size) { if (!$defined(#instance.runtime_struct_size) ||| #instance.runtime_struct_size > 0) return; #instance.runtime_struct_size = runtime_size; @@ -166,22 +170,22 @@ const char[24] PI = { }; <* The base Keccak context data structure used to generate XOF outputs. *> -struct KeccakContext @align(ulong.sizeof) +struct KeccakContext @align(ulong::size) @mustinit { - union @align(ulong.sizeof) // keccak sponge state + union @align(ulong::size) // keccak sponge state { - ulong[F / 8 / ulong.sizeof] sponge; + ulong[F / 8 / ulong::size] sponge; char[F / 8] sponge_bytes; } - union @align(ulong.sizeof) + union @align(ulong::size) { - ulong[rate(OUTPUT_BITS) / ulong.sizeof] buf_words; + ulong[rate(OUTPUT_BITS) / ulong::size] buf_words; char[rate(OUTPUT_BITS)] buf; } - usz offset; // tracking leftover bytes from absorption + sz offset; // tracking leftover bytes from absorption char runtime_delimiter; char runtime_rounds; - usz runtime_struct_size; + sz runtime_struct_size; bool is_padded; KeccakStateMachine state; } @@ -196,8 +200,8 @@ struct KeccakContext @align(ulong.sizeof) *> fn void KeccakContext.init(&self, char runtime_rounds = DEFAULT_ROUNDS, char runtime_delimiter = DEFAULT_DELIMITER) { - @init_runtime_size(self, $sizeof(*self)); - usz struct_sz = self.runtime_struct_size; + @init_runtime_size(self, @sizeof(*self)); + sz struct_sz = self.runtime_struct_size; *self = { .runtime_rounds = runtime_rounds, .runtime_delimiter = runtime_delimiter, @@ -235,15 +239,15 @@ fn void KeccakContext.finalize(&self) self.state.transition(FINALIZED); } -<* Explicitly zeros the instance based on the runtime size. A minimum `$sizeof(KeccakContext)` is assumed. *> -fn void KeccakContext.wipe(&self) @inline => mem::zero_volatile(((char*)self)[:max($sizeof(*self), self.runtime_struct_size)]); +<* Explicitly zeros the instance based on the runtime size. A minimum `@sizeof(KeccakContext)` is assumed. *> +fn void KeccakContext.wipe(&self) @inline => mem::zero_volatile(((char*)self)[:max(@sizeof(*self), self.runtime_struct_size)]); <* An internal function used to XOR the sponge with the partial "stomach" buffer (data awaiting digestion). *> fn void KeccakContext.fill_block(&self) { - usz i = 0; - for (; i < (self.offset / ulong.sizeof); i++) self.sponge[i] ^= self.buf_words[i]; - for (i = i * ulong.sizeof; i < self.offset; i++) self.sponge_bytes[i] ^= self.buf[i]; + sz i = 0; + for (; i < (self.offset / ulong::size); i++) self.sponge[i] ^= self.buf_words[i]; + for (i = i * ulong::size; i < self.offset; i++) self.sponge_bytes[i] ^= self.buf[i]; self.permute_r(); self.offset = 0; } @@ -258,11 +262,11 @@ fn void KeccakContext.absorb(&self, char[] input) self.state.transition(ABSORBING); if (!input.len) return; - usz i; + sz i; if (self.offset > 0) // handle any leftovers { - usz left = min(rate(OUTPUT_BITS) - self.offset, input.len); + sz left = min(rate(OUTPUT_BITS) - self.offset, input.len); self.buf[self.offset:left] = input[:left]; self.offset += left; @@ -274,12 +278,12 @@ fn void KeccakContext.absorb(&self, char[] input) i = left; } - bool is_aligned = 0 == (uptr)&input[i] % ulong.sizeof; + bool is_aligned = 0 == (uptr)&input[i] % ulong::size; if (is_aligned) { for (; i + rate(OUTPUT_BITS) < input.len; i += rate(OUTPUT_BITS)) { - foreach (j, c : *(ulong[rate(OUTPUT_BITS) / ulong.sizeof]*)(&input[i])) self.sponge[j] ^= c; + foreach (j, c : *(ulong[rate(OUTPUT_BITS) / ulong::size]*)(&input[i])) self.sponge[j] ^= c; self.permute_r(); } } @@ -292,7 +296,7 @@ fn void KeccakContext.absorb(&self, char[] input) } } - usz left = input.len - i; + sz left = input.len - i; if (left > 0) self.buf[:left] = input[i:left]; self.offset = left; @@ -303,9 +307,9 @@ fn void KeccakContext.absorb(&self, char[] input) *> fn void KeccakContext.pad(&self) { - usz i = 0; - for (; i < (self.offset / ulong.sizeof); i++) self.sponge[i] ^= self.buf_words[i]; - for (i = i * ulong.sizeof; i < self.offset; i++) self.sponge_bytes[i] ^= self.buf[i]; + sz i = 0; + for (; i < (self.offset / ulong::size); i++) self.sponge[i] ^= self.buf_words[i]; + for (i = i * ulong::size; i < self.offset; i++) self.sponge_bytes[i] ^= self.buf[i]; if (self.offset == rate(OUTPUT_BITS)) { @@ -332,7 +336,7 @@ fn void KeccakContext.squeeze(&self, char[] out) self.state.transition(SQUEEZING); if (!out.len) return; - usz i; + sz i; if (self.offset == rate(OUTPUT_BITS)) { @@ -343,7 +347,7 @@ fn void KeccakContext.squeeze(&self, char[] out) char[rate(OUTPUT_BITS)] localbuf; localbuf[..] = self.buf[..]; - usz left = min(rate(OUTPUT_BITS) - self.offset, out.len); + sz left = min(rate(OUTPUT_BITS) - self.offset, out.len); out[:left] = localbuf[self.offset:left]; self.offset += left; @@ -365,7 +369,7 @@ fn void KeccakContext.squeeze(&self, char[] out) self.permute_r(); } - usz left = out.len - i; + sz left = out.len - i; if (left > 0) out[i:left] = self.sponge_bytes[:left]; self.offset = left; @@ -376,7 +380,7 @@ fn void KeccakContext.squeeze(&self, char[] out) *> fn void KeccakContext.permute_r(&self) @inline { - for (usz i = RC.len - self.runtime_rounds; i < RC.len; i++) self.round(RC[i]); + for (sz i = RC.len - self.runtime_rounds; i < RC.len; i++) self.round(RC[i]); } <* diff --git a/test/stdlib/src/crypto/keccak/kmac.c3 b/test/stdlib/src/crypto/keccak/kmac.c3 index e7f0c37..dd13176 100644 --- a/test/stdlib/src/crypto/keccak/kmac.c3 +++ b/test/stdlib/src/crypto/keccak/kmac.c3 @@ -95,7 +95,7 @@ macro void xof_into($security_level, char[] into, char[] key, char[] data, Strin @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct KmacXOF +struct KmacXOF @mustinit { inline Kmac{SECURITY_LEVEL} k; } @@ -108,7 +108,7 @@ struct KmacXOF *> fn void KmacXOF.init(&self, char[] key, String customizer = "") { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.k.is_xof_mode = true; self.k.init(key, customizer: customizer); } @@ -118,7 +118,7 @@ fn void KmacXOF.init(&self, char[] key, String customizer = "") @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct Kmac +struct Kmac @mustinit { inline CShake{SECURITY_LEVEL} c; bool is_xof_mode; @@ -132,7 +132,7 @@ struct Kmac *> fn void Kmac.init(&self, char[] key, String customizer = "") { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.c.init(KMAC, customizer); self.c.update(nist::encoded_rate_bytes(SECURITY_LEVEL)); diff --git a/test/stdlib/src/crypto/keccak/nist.c3 b/test/stdlib/src/crypto/keccak/nist.c3 index 0fec33a..481f7f4 100644 --- a/test/stdlib/src/crypto/keccak/nist.c3 +++ b/test/stdlib/src/crypto/keccak/nist.c3 @@ -58,17 +58,17 @@ macro char[] encoded_rate_bytes($security_level) @const @param length : "The length value to encode." @param as_direct_bytes : "If set to `true`, encode `length` directly, without converting the value to a bit count." - @require into.len >= uint128.sizeof + 2 : "The storage buffer must have adequate space for all possible 16-byte integer values (>= 18 bytes)." + @require into.len >= int128::size + 2 : "The storage buffer must have adequate space for all possible 16-byte integer values (>= 18 bytes)." @return "The width of the encoded length in bytes." *> -fn usz encode_length_into(char[] into, NistEncodingType direction, uint128 length, bool as_direct_bytes = false) +fn sz encode_length_into(char[] into, NistEncodingType direction, int128 length, bool as_direct_bytes = false) { if (!as_direct_bytes) length <<= 3; int bits = 8; for (; math::pow(2, bits) <= length; bits += 8); - char bytes = (char)bits / 8; - uint128 swapped_length = !env::BIG_ENDIAN ??? bswap(length) : length; + char bytes = (char)(bits / 8); + int128 swapped_length = !env::BIG_ENDIAN ??? bswap(length) : length; into[1:bytes] = @as_char_view(swapped_length)[^bytes..]; switch (direction) { @@ -90,9 +90,9 @@ fn usz encode_length_into(char[] into, NistEncodingType direction, uint128 lengt @return "A slice pointing to a stack-allocated byte array, containing the fully encoded length." *> -macro char[] encode_length(NistEncodingType direction, uint128 length, bool as_direct_bytes = false) +macro char[] encode_length(NistEncodingType direction, int128 length, bool as_direct_bytes = false) { - char[uint128.sizeof + 2] result; - usz bytes = encode_length_into(result[..], direction, length, as_direct_bytes: as_direct_bytes); + char[int128::size + 2] result; + sz bytes = encode_length_into(result[..], direction, length, as_direct_bytes: as_direct_bytes); return direction == LEFT ? result[:bytes + 1] : result[1:bytes + 1]; } diff --git a/test/stdlib/src/crypto/keccak/parallel_hash.c3 b/test/stdlib/src/crypto/keccak/parallel_hash.c3 index dc6d264..1d7d3fc 100644 --- a/test/stdlib/src/crypto/keccak/parallel_hash.c3 +++ b/test/stdlib/src/crypto/keccak/parallel_hash.c3 @@ -29,28 +29,28 @@ macro char[*] apply_simple_cshake($security_level, char[] data) @local @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct ParallelHash +struct ParallelHash @mustinit { inline CShake{SECURITY_LEVEL} c; Allocator allocator; bool is_xof_mode; bool is_padded; - usz block_size; - usz threads; - usz intermediate_xof_size; + sz block_size; + sz threads; + sz intermediate_xof_size; char[] internal; // handle leftovers; only ever updated by a single thread (ID 0) - usz offset; + sz offset; uint128 total_len; } <* Define a worker context, passed to each independent processing thread for hashing operations. *> struct WorkerThreadContext @local { - usz thread_id; + sz thread_id; void* context; - usz consume_len; - usz blocks; - usz xof_count; + sz consume_len; + sz blocks; + sz xof_count; char[] data; char[] out; } @@ -72,8 +72,8 @@ struct WorkerThreadContext @local @return "An array of `$outlen_bytes` bytes containing the hash." *> -macro char[*] xof($security_level, $outlen_bytes, Allocator allocator, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro char[*] xof($security_level, $outlen_bytes, Allocator allocator, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) { char [$outlen_bytes] result @noinit; xof_into($security_level, allocator, result[..], data, block_size, threads, customizer, sequential); @@ -81,8 +81,8 @@ macro char[*] xof($security_level, $outlen_bytes, Allocator allocator, char[] da } <* An alias for ParallelHash's `xof` that implicitly uses the temp allocator for internal memory management. *> -macro char[*] txof($security_level, $outlen_bytes, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro char[*] txof($security_level, $outlen_bytes, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) => xof($security_level, $outlen_bytes, tmem, data, block_size, threads, customizer, sequential); <* @@ -100,8 +100,8 @@ macro char[*] txof($security_level, $outlen_bytes, char[] data, usz block_size, @require @in($security_level, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." @require into.len > 0 : "You must use an output length over zero bytes." *> -macro void xof_into($security_level, Allocator allocator, char[] into, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro void xof_into($security_level, Allocator allocator, char[] into, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) { ParallelHashXOF{$security_level} c; c.init(allocator, block_size: block_size, threads: threads, customizer: customizer); @@ -110,8 +110,8 @@ macro void xof_into($security_level, Allocator allocator, char[] into, char[] da } <* An alias for ParallelHash's `xof_into` that implicitly uses the temp allocator for internal memory management. *> -macro void txof_into($security_level, char[] into, char[] data, usz block_size, - usz threads, String customizer = "", bool sequential = false) +macro void txof_into($security_level, char[] into, char[] data, sz block_size, + sz threads, String customizer = "", bool sequential = false) => xof_into($security_level, tmem, into, data, block_size, threads, customizer, sequential); <* @@ -131,8 +131,8 @@ macro void txof_into($security_level, char[] into, char[] data, usz block_size, @return "An array of `$outlen_bytes` bytes containing the hash." *> -macro hash($security_level, $outlen_bytes, Allocator allocator, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro hash($security_level, $outlen_bytes, Allocator allocator, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) { char [$outlen_bytes] result @noinit; hash_into($security_level, allocator, result[..], data, block_size, threads, customizer, sequential); @@ -140,8 +140,8 @@ macro hash($security_level, $outlen_bytes, Allocator allocator, char[] data, usz } <* An alias for ParallelHash's `hash` that implicitly uses the temp allocator for internal memory management. *> -macro thash($security_level, $outlen_bytes, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro thash($security_level, $outlen_bytes, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) => hash($security_level, $outlen_bytes, tmem, data, block_size, threads, customizer, sequential); <* @@ -159,8 +159,8 @@ macro thash($security_level, $outlen_bytes, char[] data, usz block_size, @require @in($security_level, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." @require into.len > 0 : "You must use an output length over zero bytes." *> -macro hash_into($security_level, Allocator allocator, char[] into, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro hash_into($security_level, Allocator allocator, char[] into, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) { ParallelHash{$security_level} c; c.init(allocator, block_size: block_size, threads: threads, customizer: customizer); @@ -169,8 +169,8 @@ macro hash_into($security_level, Allocator allocator, char[] into, char[] data, } <* An alias for ParallelHash's `hash_into` that implicitly uses the temp allocator for internal memory management. *> -macro thash_into($security_level, char[] into, char[] data, usz block_size, - usz threads = 0, String customizer = "", bool sequential = false) +macro thash_into($security_level, char[] into, char[] data, sz block_size, + sz threads = 0, String customizer = "", bool sequential = false) => hash_into($security_level, tmem, into, data, block_size, threads, customizer, sequential); <* @@ -178,9 +178,9 @@ macro thash_into($security_level, char[] into, char[] data, usz block_size, This causes the output value to be completely different and not dependent on the length of the output buffer. To better understand this, or to see the reference, search for NIST SP 800-185. - @require @in($security_level, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." + @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct ParallelHashXOF +struct ParallelHashXOF @mustinit { inline ParallelHash{SECURITY_LEVEL} ph; } @@ -193,14 +193,14 @@ struct ParallelHashXOF @param threads : "How many lanes should be used for parallel processing. If set to 0, defaults to the amount of CPU cores available." @param customizer : "An optional, variable-length customization string used for further domain separation." *> -macro void ParallelHashXOF.init(&self, Allocator allocator, usz block_size = MAX_BLOCK_SIZE >> 3, usz threads = 0, String customizer = "") +macro void ParallelHashXOF.init(&self, Allocator allocator, sz block_size = MAX_BLOCK_SIZE >> 3, sz threads = 0, String customizer = "") { self.ph.init(allocator, block_size, threads, customizer); self.is_xof_mode = true; } <* An alias to initialize an XOF-variant hash context with the temp allocator. *> -macro void ParallelHashXOF.tinit(&self, usz block_size = MAX_BLOCK_SIZE >> 3, String customizer = "", usz threads = 0) +macro void ParallelHashXOF.tinit(&self, sz block_size = MAX_BLOCK_SIZE >> 3, String customizer = "", sz threads = 0) => self.init(tmem, block_size, threads, customizer); <* @@ -211,9 +211,9 @@ macro void ParallelHashXOF.tinit(&self, usz block_size = MAX_BLOCK_SIZE >> 3, St @param threads : "How many lanes should be used for parallel processing. If set to 0, defaults to the amount of CPU cores available." @param customizer : "An optional, variable-length customization string used for further domain separation." *> -fn void ParallelHash.init(&self, Allocator allocator, usz block_size = MAX_BLOCK_SIZE >> 3, usz threads = 0, String customizer = "") +fn void ParallelHash.init(&self, Allocator allocator, sz block_size = MAX_BLOCK_SIZE >> 3, sz threads = 0, String customizer = "") { - threads = threads ?: (usz)cpu::native_cpu(); + threads = threads ?: (sz)cpu::native_cpu(); bool use_xof = self.is_xof_mode; *self = { @@ -223,16 +223,16 @@ fn void ParallelHash.init(&self, Allocator allocator, usz block_size = MAX_BLOCK .threads = math::clamp(threads, 1, MAX_THREADS), .intermediate_xof_size = SECURITY_LEVEL >> 2, }; - self.internal = allocator::new_array(allocator, char, self.block_size); + self.internal = alloc::new_array(allocator, char, self.block_size); - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.c.init(PARALLEL_HASH, customizer); self.c.update(nist::encode_length(LEFT, self.block_size, true)); } <* An alias to initialize a non-XOF-variant hash context with the temp allocator. *> -macro void ParallelHash.tinit(&self, usz block_size = MAX_BLOCK_SIZE >> 3, String customizer = "", usz threads = 0) +macro void ParallelHash.tinit(&self, sz block_size = MAX_BLOCK_SIZE >> 3, String customizer = "", sz threads = 0) => self.init(tmem, block_size, threads, customizer); <* @@ -244,11 +244,11 @@ macro void ParallelHash.tinit(&self, usz block_size = MAX_BLOCK_SIZE >> 3, Strin fn void ParallelHash.update(&self, char[] data, bool sequential = false) { if (!data.len) return; - self.total_len += data.len; + self.total_len += (uint128)data.len; if (self.offset > 0) { - usz left = min(self.block_size - self.offset, data.len); + sz left = min(self.block_size - self.offset, data.len); if (left > 0) { @@ -267,70 +267,73 @@ fn void ParallelHash.update(&self, char[] data, bool sequential = false) // If fewer than two blocks were received, then don't thread at all. Same for disabled threading of course. // This is a very linear process: apply cSHAKE to each block and immediately push its result into the main cSHAKE context. - if (sequential || data.len < self.block_size * 2 || !env::NATIVE_THREADING) - { + $if !env::NATIVE_THREADING: for (; data.len >= self.block_size; data = data[self.block_size..]) { self.c.update(apply_simple_cshake(SECURITY_LEVEL, data[:self.block_size])[..]); } - } - else - { - $if !env::NATIVE_THREADING: - unreachable("NATIVE_THREADING not set, should not have entered this branch"); - $endif - - // Start by allocating the necessary threading resources, even if they won't all be used - Thread[] workers = allocator::new_array(self.allocator, Thread, self.threads); - defer allocator::free(self.allocator, workers); - - WorkerThreadContext[] contexts = allocator::new_array(self.allocator, WorkerThreadContext, self.threads); - defer + $else + if (sequential || data.len < self.block_size * 2) { - foreach (ctx : contexts) if (ctx.out.ptr) allocator::free(self.allocator, ctx.out); // free each returned byte array - allocator::free(self.allocator, contexts); + for (; data.len >= self.block_size; data = data[self.block_size..]) + { + self.c.update(apply_simple_cshake(SECURITY_LEVEL, data[:self.block_size])[..]); + } } - - // Round-robin distribute sizes between all thread indices. - // This is a bit of flair. There's probably an easier way, but luckily this isn't the performance-critical section. - usz remainder = data.len; - for (usz i = 0; remainder >= self.block_size; i++, remainder -= self.block_size) + else { - contexts[i % self.threads].consume_len += self.block_size; - } + // Start by allocating the necessary threading resources, even if they won't all be used + Thread[] workers = alloc::new_array(self.allocator, Thread, self.threads); + defer alloc::free(self.allocator, workers); - // Now distribute the actual work with a context for each thread. - // Each thread has a pool of memory `.out`, where it can store an array of its sequential XOF results - // This array is later folded into the main context's cSHAKE context. This needs to be done IN ORDER, which is - // why these are preserved during parallel processing.. - for (usz i = self.threads; i > 0; i--) - { - usz to_consume = contexts[i - 1].consume_len; - usz blocks = to_consume / self.block_size; - - contexts[i - 1] = (WorkerThreadContext){ - .thread_id = i - 1, - .context = self, - .blocks = blocks, - .xof_count = 0, - .data = data[:to_consume], - .out = blocks > 0 ? allocator::new_array(self.allocator, char, self.intermediate_xof_size * blocks) : {}, - }; - workers[i - 1].create(&threaded_update{SECURITY_LEVEL}, &contexts[i - 1])!!; // go! - data = data[to_consume..]; - } - foreach_r (w : workers) w.join()!!; // await all workers - foreach_r (&t : contexts[:self.threads]) - { - // Gorge `self` with all produced XOF chains, absorbing them into the main context's cSHAKE context as described. - for (usz i = 0; i < t.blocks; i++) + WorkerThreadContext[] contexts = alloc::new_array(self.allocator, WorkerThreadContext, self.threads); + defer { - self.c.update(t.out[i * self.intermediate_xof_size : self.intermediate_xof_size]); + foreach (ctx : contexts) if (ctx.out.ptr) alloc::free(self.allocator, ctx.out); // free each returned byte array + alloc::free(self.allocator, contexts); } - } - assert(data.len == remainder, "Not all input data was accounted for; cannot proceed."); - } + // Round-robin distribute sizes between all thread indices. + // This is a bit of flair. There's probably an easier way, but luckily this isn't the performance-critical section. + sz remainder = data.len; + for (sz i = 0; remainder >= self.block_size; i++, remainder -= self.block_size) + { + contexts[i % self.threads].consume_len += self.block_size; + } + + // Now distribute the actual work with a context for each thread. + // Each thread has a pool of memory `.out`, where it can store an array of its sequential XOF results + // This array is later folded into the main context's cSHAKE context. This needs to be done IN ORDER, which is + // why these are preserved during parallel processing.. + for (sz i = self.threads; i > 0; i--) + { + sz to_consume = contexts[i - 1].consume_len; + sz blocks = to_consume / self.block_size; + + contexts[i - 1] = (WorkerThreadContext){ + .thread_id = i - 1, + .context = self, + .blocks = blocks, + .xof_count = 0, + .data = data[:to_consume], + .out = blocks > 0 ? alloc::new_array(self.allocator, char, self.intermediate_xof_size * blocks) : {}, + }; + workers[i - 1].create(&threaded_update{SECURITY_LEVEL}, &contexts[i - 1])!!; // go! + data = data[to_consume..]; + } + foreach_r (w : workers) w.join()!!; // await all workers + foreach_r (&t : contexts[:self.threads]) + { + // Gorge `self` with all produced XOF chains, absorbing them into the main context's cSHAKE context as described. + for (sz i = 0; i < t.blocks; i++) + { + self.c.update(t.out[i * self.intermediate_xof_size : self.intermediate_xof_size]); + } + } + + assert(data.len == remainder, "Not all input data was accounted for; cannot proceed."); + } + $endif // If there's some leftover data, then hold it for either the next call to 'update' or 'final'. if (data.len > 0) @@ -374,7 +377,7 @@ fn void ParallelHash.final(&self, char[] out) { defer { - allocator::free(self.allocator, self.internal); + alloc::free(self.allocator, self.internal); self.finalize(); } self.squeeze(out); diff --git a/test/stdlib/src/crypto/keccak/sha3.c3 b/test/stdlib/src/crypto/keccak/sha3.c3 index 446cf69..0f999d5 100644 --- a/test/stdlib/src/crypto/keccak/sha3.c3 +++ b/test/stdlib/src/crypto/keccak/sha3.c3 @@ -11,7 +11,7 @@ const char IPAD @local = 0x36; const char OPAD @local = 0x5c; <* Valid hash digest sizes; transparently equates to the valid Keccak security levels. *> -const usz[] DIGEST_SIZES = keccak::SECURITY_LEVELS; +const sz[] DIGEST_SIZES = keccak::SECURITY_LEVELS; <* The Keccak domain-separation delimiter to use specifically for SHA-3. *> const DEFAULT_DELIMITER = 0x06; @@ -122,7 +122,7 @@ macro void simple_hmac_into($hash_bits, char[] into, char[] key, char[] data) @return "An array of `$outlen_bytes` bytes containing the hash." *> -macro char[*] hmac($hash_bits, usz $tag_size, char[] key, char[] data) +macro char[*] hmac($hash_bits, sz $tag_size, char[] key, char[] data) { char[$tag_size] result @noinit; hmac_into($hash_bits, result[..], key, data); @@ -150,21 +150,24 @@ macro char[*] hmac($hash_bits, usz $tag_size, char[] key, char[] data) *> macro void hmac_into($hash_bits, char[] into, char[] key, char[] data) { - usz $block_size = keccak::rate($hash_bits); - char[$block_size] k_prime; - if (key.len > $block_size) + (fn void(char[] into, char[] key, char[] data) { - hash_into($hash_bits, k_prime[:$hash_bits / 8], key); - } - else - { - k_prime[:key.len] = key[..]; - } - char[$block_size] _ipad @noinit; _ipad[..] = IPAD; array::@zip_into(_ipad[..], k_prime, fn (a, b) => a ^ b); - char[$block_size] _opad @noinit; _opad[..] = OPAD; array::@zip_into(_opad[..], k_prime, fn (a, b) => a ^ b); - - char[$hash_bits / 8] intermediate_ipad = simple_hmac($hash_bits, _ipad[..], data); - simple_hmac_into($hash_bits, into, _opad[..], intermediate_ipad[..]); + sz $block_size = keccak::rate($hash_bits); + char[$block_size] k_prime; + if (key.len > $block_size) + { + hash_into($hash_bits, k_prime[:$hash_bits / 8], key); + } + else + { + k_prime[:key.len] = key[..]; + } + char[$block_size] _ipad @noinit; _ipad[..] = IPAD; array::@zip_into(_ipad[..], k_prime, fn (a, b) => a ^ b); + char[$block_size] _opad @noinit; _opad[..] = OPAD; array::@zip_into(_opad[..], k_prime, fn (a, b) => a ^ b); + + char[$hash_bits / 8] intermediate_ipad = simple_hmac($hash_bits, _ipad[..], data); + simple_hmac_into($hash_bits, into, _opad[..], intermediate_ipad[..]); + })(into, key, data); } <* @@ -172,7 +175,7 @@ macro void hmac_into($hash_bits, char[] into, char[] key, char[] data) @require @in(HASH_BITS, ...DIGEST_SIZES) : "You must use a valid SHA-3 digest size." *> -struct Sha3 +struct Sha3 @mustinit { inline Keccak{HASH_BITS} k; } @@ -182,6 +185,6 @@ struct Sha3 *> fn void Sha3.init(&self) { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.k.init(runtime_delimiter: DEFAULT_DELIMITER); } diff --git a/test/stdlib/src/crypto/keccak/shake.c3 b/test/stdlib/src/crypto/keccak/shake.c3 index 877b9f1..78bc83f 100644 --- a/test/stdlib/src/crypto/keccak/shake.c3 +++ b/test/stdlib/src/crypto/keccak/shake.c3 @@ -6,7 +6,7 @@ module std::crypto::shake; import std::crypto @public; <* The set of usable security levels that are valid for this implementation of SHAKE and Keccak. *> -const usz[] USABLE_LEVELS = { 128, 256 }; +const sz[] USABLE_LEVELS = { 128, 256 }; <* The Keccak domain-separation delimiter to use specifically for SHAKE. *> const DEFAULT_DELIMITER = 0x1f; @@ -63,11 +63,11 @@ alias hash_into = xof_into; @require @in(SECURITY_LEVEL, ...USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct Shake +struct Shake @mustinit { inline Keccak{SECURITY_LEVEL} k; bool is_padded; - usz offset; + sz offset; char[keccak::rate(SECURITY_LEVEL)] buf; } @@ -79,7 +79,7 @@ struct Shake *> fn void Shake.init(&self, char rounds_override = DEFAULT_ROUNDS, char delimiter = DEFAULT_DELIMITER) { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.k.init(rounds_override, delimiter); } @@ -99,7 +99,7 @@ fn void Shake.squeeze(&self, char[] into) char[] out = into; if (self.offset > 0) { - usz left = min(self.buf.len - self.offset, out.len); + sz left = min(self.buf.len - self.offset, out.len); if (left > 0) { out[:left] = self.buf[self.offset:left]; diff --git a/test/stdlib/src/crypto/keccak/tuple_hash.c3 b/test/stdlib/src/crypto/keccak/tuple_hash.c3 index 236c36b..66bbd6d 100644 --- a/test/stdlib/src/crypto/keccak/tuple_hash.c3 +++ b/test/stdlib/src/crypto/keccak/tuple_hash.c3 @@ -99,7 +99,7 @@ macro void xof_into($security_level, char[] into, char[] data, char[]... extras, @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct TupleHashXOF +struct TupleHashXOF @mustinit { inline TupleHash{SECURITY_LEVEL} t; } @@ -111,7 +111,7 @@ struct TupleHashXOF *> fn void TupleHashXOF.init(&self, String customizer = "") { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.t.is_xof_mode = true; self.t.init(customizer); } @@ -121,7 +121,7 @@ fn void TupleHashXOF.init(&self, String customizer = "") @require @in(SECURITY_LEVEL, ...cshake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct TupleHash +struct TupleHash @mustinit { inline CShake{SECURITY_LEVEL} c; bool is_xof_mode; @@ -134,7 +134,7 @@ struct TupleHash *> fn void TupleHash.init(&self, String customizer = "") { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.c.init(TUPLE_HASH, customizer); } diff --git a/test/stdlib/src/crypto/keccak/turbo_shake.c3 b/test/stdlib/src/crypto/keccak/turbo_shake.c3 index 86b47e0..418bbbe 100644 --- a/test/stdlib/src/crypto/keccak/turbo_shake.c3 +++ b/test/stdlib/src/crypto/keccak/turbo_shake.c3 @@ -69,7 +69,7 @@ alias hash_into = xof_into; @require @in(SECURITY_LEVEL, ...shake::USABLE_LEVELS) : "Shake and Shake-derived structures must use a valid security level." *> -struct TurboShake +struct TurboShake @mustinit { inline Shake{SECURITY_LEVEL} s; } @@ -81,6 +81,6 @@ struct TurboShake *> fn void TurboShake.init(&self, char delimiter = DEFAULT_DELIMITER) { - keccak::@init_runtime_size(self, $sizeof(*self)); + keccak::@init_runtime_size(self, @sizeof(*self)); self.s.init(DEFAULT_ROUNDS, delimiter); } diff --git a/test/stdlib/src/crypto/rc4.c3 b/test/stdlib/src/crypto/rc4.c3 index 18fc05a..fad5e05 100644 --- a/test/stdlib/src/crypto/rc4.c3 +++ b/test/stdlib/src/crypto/rc4.c3 @@ -52,10 +52,10 @@ fn void Rc4.crypt(&self, char[] in, char[] out) uint i = self.i; uint j = self.j; char* state = &self.state; - isz len = in.len; + sz len = in.len; foreach (idx, c : in) { - i = (i + 1) & 0xFF; + i = (i + 1u) & 0xFF; j = (j + state[i]) & 0xFF; @swap(state[i], state[j]); out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF]; diff --git a/test/stdlib/src/encoding/base32.c3 b/test/stdlib/src/encoding/base32.c3 index 29dd918..fea465a 100644 --- a/test/stdlib/src/encoding/base32.c3 +++ b/test/stdlib/src/encoding/base32.c3 @@ -22,8 +22,8 @@ const char DEFAULT_PAD = '='; *> fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) { - char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding)); - return encode_buffer(src, dst, padding, alphabet); + char[] dst = alloc::alloc_array(allocator, char, encode_len(src.len, padding)); + return encode_into(dst, src, padding, alphabet); } <* @@ -36,8 +36,8 @@ fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, B *> fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) { - char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding)); - return decode_buffer(src, dst, padding, alphabet); + char[] dst = alloc::alloc_array(allocator, char, decode_len(src.len, padding)); + return decode_into(dst, src, padding, alphabet); } fn String? tencode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet); @@ -50,11 +50,11 @@ fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alph @require padding < 0xFF : "Invalid padding character" @return "Length in bytes of the decoded data." *> -fn usz decode_len(usz n, char padding) +fn sz decode_len(sz n, char padding) { if (padding) return (n / 8) * 5; // no padding - usz trailing = n % 8; + sz trailing = n % 8; return n / 8 * 5 + (trailing * 5 ) / 8; } @@ -65,13 +65,13 @@ fn usz decode_len(usz n, char padding) @require padding < 0xFF : "Invalid padding character" @return "Length in bytes of the encoded data." *> -fn usz encode_len(usz n, char padding) +fn sz encode_len(sz n, char padding) { // A character is encoded into 8 x 5-bit blocks. if (padding) return (n + 4) / 5 * 8; // no padding - usz trailing = n % 5; + sz trailing = n % 5; return n / 5 * 8 + (trailing * 8 + 4) / 5; } @@ -86,16 +86,16 @@ fn usz encode_len(usz n, char padding) @return "The resulting dst buffer" @return? encoding::INVALID_PADDING, encoding::INVALID_CHARACTER *> -fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) +fn char[]? decode_into(char[] dst, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) { if (src.len == 0) return dst[:0]; char* dst_ptr = dst; - usz dn = decode_len(src.len, padding); - usz n; + sz dn = decode_len(src.len, padding); + sz n; char[8] buf; while (src.len > 0 && dst.len > 0) { - usz i @noinit; + sz i @noinit; // load 8 bytes into buffer for (i = 0; i < 8; i++) { @@ -158,6 +158,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas return dst_ptr[:n]; } + <* Encode the content of src into dst, which must be properly sized. @param [in] src : "The input to be encoded." @@ -168,16 +169,16 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas @require dst.len >= encode_len(src.len, padding) : "Destination buffer too small" @return "The encoded size." *> -fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) +fn String encode_into(char[] dst, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) { if (src.len == 0) return (String)dst[:0]; char* dst_ptr = dst; - usz n = (src.len / 5) * 5; - usz dn = encode_len(src.len, padding); + sz n = (src.len / 5) * 5; + sz dn = encode_len(src.len, padding); uint msb, lsb; - for (usz i = 0; i < n; i += 5) + for (sz i = 0; i < n; i += 5) { // to fit 40 bits we need two 32-bit uints msb = (uint)src[i] << 24 | (uint)src[i+1] << 16 @@ -198,7 +199,7 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base dst = dst[8..]; } - usz trailing = src.len - n; + sz trailing = src.len - n; if (trailing == 0) return (String)dst_ptr[:dn]; msb = 0; @@ -228,7 +229,7 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base // add the padding if (padding > 0) { - for (usz i = (trailing * 8 / 5) + 1; i < 8; i++) + for (sz i = (trailing * 8 / 5) + 1; i < 8; i++) { dst[i] = padding; } diff --git a/test/stdlib/src/encoding/base64.c3 b/test/stdlib/src/encoding/base64.c3 index 9e4a088..327ba78 100644 --- a/test/stdlib/src/encoding/base64.c3 +++ b/test/stdlib/src/encoding/base64.c3 @@ -45,14 +45,14 @@ const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345 fn String encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) { - char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding)); - return encode_buffer(src, dst, padding, alphabet); + char[] dst = alloc::alloc_array(allocator, char, encode_len(src.len, padding)); + return encode_into(dst, src, padding, alphabet); } fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) { - char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!; - return decode_buffer(src, dst, padding, alphabet); + char[] dst = alloc::alloc_array(allocator, char, decode_len(src.len, padding))!; + return decode_into(dst, src, padding, alphabet); } fn String tencode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet); @@ -66,10 +66,10 @@ fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alph @require padding < 0xFF : "Invalid padding character" @return "The size of the input once encoded." *> -fn usz encode_len(usz n, char padding) +fn sz encode_len(sz n, char padding) { if (padding) return (n + 2) / 3 * 4; - usz trailing = n % 3; + sz trailing = n % 3; return n / 3 * 4 + (trailing * 4 + 2) / 3; } @@ -81,10 +81,10 @@ fn usz encode_len(usz n, char padding) @return "The size of the input once decoded." @return? encoding::INVALID_PADDING *> -fn usz? decode_len(usz n, char padding) +fn sz? decode_len(sz n, char padding) { - usz dn = n / 4 * 3; - usz trailing = n % 4; + sz dn = n / 4 * 3; + sz trailing = n % 4; if (padding) { if (trailing != 0) return encoding::INVALID_PADDING~; @@ -95,6 +95,7 @@ fn usz? decode_len(usz n, char padding) return dn + trailing * 3 / 4; } + <* Encode the content of src into dst, which must be properly sized. @param src : "The input to be encoded." @@ -104,13 +105,13 @@ fn usz? decode_len(usz n, char padding) @require padding < 0xFF : "Invalid padding character" @return "The encoded size." *> -fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) +fn String encode_into(char[] dst, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) { if (src.len == 0) return (String)dst[:0]; - usz dn = encode_len(src.len, padding); + sz dn = encode_len(src.len, padding); char* dst_ptr = dst; assert(dst.len >= dn); - usz trailing = src.len % 3; + sz trailing = src.len % 3; char[] src3 = src[:^trailing]; while (src3.len > 0) @@ -154,6 +155,7 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base return (String)dst_ptr[:dn]; } + <* Decode the content of src into dst, which must be properly sized. @param src : "The input to be decoded." @@ -165,13 +167,13 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base @return "The decoded data." @return? encoding::INVALID_CHARACTER, encoding::INVALID_PADDING *> -fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) +fn char[]? decode_into(char[] dst, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) { if (src.len == 0) return dst[:0]; - usz dn = decode_len(src.len, padding)!; + sz dn = decode_len(src.len, padding)!; assert(dst.len >= dn); - usz trailing = src.len % 4; + sz trailing = src.len % 4; char* dst_ptr = dst; char[] src4 = src; switch diff --git a/test/stdlib/src/encoding/codepage.c3 b/test/stdlib/src/encoding/codepage.c3 index 94f930d..cd872a0 100644 --- a/test/stdlib/src/encoding/codepage.c3 +++ b/test/stdlib/src/encoding/codepage.c3 @@ -123,7 +123,7 @@ fn CodePage? by_name(String charset_name) => @pool() { String name = charset_name.treplace("_","-"); name.convert_to_lower(); - foreach (page : CodePage.values) + foreach (page : CodePage::values) { if (page.name == charset_name) return page; } @@ -132,25 +132,25 @@ fn CodePage? by_name(String charset_name) => @pool() fn String? decode(Allocator allocator, char[] src, CodePage code_page) { - char[] dst = allocator::alloc_array(allocator, char, decode_len(src, code_page)); - return decode_buffer(src, dst, code_page); + char[] dst = alloc::alloc_array(allocator, char, decode_len(src, code_page)); + return decode_into(dst, src, code_page); } <* Decode a code-page byte buffer into a UTF‑8 string. - @param src : "Input byte array in the given code page." @param dst : "Destination output string in UTF-8." + @param src : "Input byte array in the given code page." @param code_page : "Code page for this encoding." @return "String in UTF-8." *> -fn String? decode_buffer(char[] src, char[] dst, CodePage code_page) +fn String? decode_into(char[] dst, char[] src, CodePage code_page) { - usz n = 0; + sz n = 0; CodePageTable *table = code_page.table; foreach (c: src) { - usz pos = (usz)c * 4; + sz pos = (sz)c * 4; char len = table.to_codepoint[pos]; dst[n:len] = table.to_codepoint[pos+1:len]; @@ -162,8 +162,8 @@ fn String? decode_buffer(char[] src, char[] dst, CodePage code_page) fn char[]? encode(Allocator allocator, char[] src, CodePage code_page, char replacement = REPLACEMENT_CHAR) { - char[] dst = allocator::alloc_array(allocator, char, encode_len(src)); - return encode_buffer(src, dst, code_page, replacement); + char[] dst = alloc::alloc_array(allocator, char, encode_len(src)); + return encode_into(dst, src, code_page, replacement); } const uint MASK @private = (1u << 24) - 1; @@ -171,28 +171,28 @@ const uint MASK @private = (1u << 24) - 1; <* Encode a UTF‑8 string into a single‑byte code page. - @param src : "Input byte array in UTF-8" @param dst : "Destination output byte array in the target code page" + @param src : "Input byte array in UTF-8" @param code_page : "Code page for this encoding." @param replacement : "Byte to emit when Unicode scalar cannot be represented in the target code page." @return "Byte array in the given code page." *> -fn char[]? encode_buffer(char[] src, char[] dst, CodePage code_page, char replacement = REPLACEMENT_CHAR) +fn char[]? encode_into(char[] dst, char[] src, CodePage code_page, char replacement = REPLACEMENT_CHAR) { // Unpack the packed reverse table once into a local uint[256] view. uint[256] from_map; CodePageTable *table = code_page.table; - for (usz i = 0; i < 256; i++) + for (sz i = 0; i < 256; i++) { UIntLE *val = (UIntLE*)&table.from_codepoint[i * 4]; from_map[i] = mem::load(val, 1).val; } - usz out = 0; - usz n = src.len; - for (usz i = 0; i < n; ) + sz out = 0; + sz n = src.len; + for (sz i = 0; i < n; ) { - usz rem = n - i; + sz rem = n - i; if (rem > 4) rem = 4; Char32 codepoint = conv::utf8_to_char32(&src[i], &rem)!; @@ -200,7 +200,7 @@ fn char[]? encode_buffer(char[] src, char[] dst, CodePage code_page, char replac // Binary search for codepoint in low 24 bits of each entry. // Returned index is between [0..from_map.len). - usz index = sort::binarysearch(from_map[..], (uint)codepoint, fn int(uint lhs, uint rhs) => (int)(lhs & MASK) - (int)(rhs & MASK)); + sz index = sort::binarysearch(from_map[..], (uint)codepoint, fn int(uint lhs, uint rhs) => (int)(lhs & MASK) - (int)(rhs & MASK)); uint entry = from_map[index]; if ((entry & MASK) == (uint)codepoint) @@ -223,11 +223,11 @@ fn char[]? encode_buffer(char[] src, char[] dst, CodePage code_page, char replac @param src : "Input byte array in the given code page." @param code_page : "Code page for this encoding." *> -fn usz decode_len(char[] src, CodePage code_page) @inline +fn sz decode_len(char[] src, CodePage code_page) @inline { - usz n; + sz n; CodePageTable *table = code_page.table; - foreach (usz c: src) n += table.to_codepoint[c *4 ]; + foreach (sz c: src) n += table.to_codepoint[c *4 ]; return n; } @@ -236,7 +236,7 @@ fn usz decode_len(char[] src, CodePage code_page) @inline encoding src from UTF‑8 to a single‑byte code page. @param src : "Input byte array in UTF-8" *> -fn usz encode_len(char[] src) @inline +fn sz encode_len(char[] src) @inline { return conv::utf8_codepoints((String)src); } diff --git a/test/stdlib/src/encoding/csv.c3 b/test/stdlib/src/encoding/csv.c3 index ef7ab26..0babace 100644 --- a/test/stdlib/src/encoding/csv.c3 +++ b/test/stdlib/src/encoding/csv.c3 @@ -15,12 +15,12 @@ struct CsvRow (Printable) Allocator allocator; } -fn usz? CsvRow.to_format(&self, Formatter* f) @dynamic +fn sz? CsvRow.to_format(&self, Formatter* f) @dynamic { return f.printf("%s", self.list); } -fn usz CsvRow.len(&self) @operator(len) +fn sz CsvRow.len(&self) @operator(len) { return self.list.len; } @@ -28,7 +28,7 @@ fn usz CsvRow.len(&self) @operator(len) <* @require col < self.list.len *> -fn String CsvRow.get_col(&self, usz col) @operator([]) +fn String CsvRow.get_col(&self, sz col) @operator([]) { return self.list[col]; } @@ -44,7 +44,7 @@ fn void CsvReader.init(&self, InStream stream, String separator = ",") fn CsvRow? CsvReader.read_row(self, Allocator allocator) { String row = io::readline(allocator, self.stream)!; - defer catch allocator::free(allocator, row); + defer catch alloc::free(allocator, row); String[] list = row.split(allocator, self.separator); return { list, row, allocator }; } @@ -59,8 +59,8 @@ fn CsvRow? CsvReader.tread_row(self) *> fn void CsvRow.free(&self) { - allocator::free(self.allocator, self.list); - allocator::free(self.allocator, self.row); + alloc::free(self.allocator, self.list); + alloc::free(self.allocator, self.row); self.allocator = null; } @@ -69,7 +69,7 @@ fn void? CsvReader.skip_row(self) @maydiscard => @pool() (void)io::treadline(self.stream); } -macro void? @each_row(InStream stream, String separator = ",", int max_rows = int.max; @body(String[] row)) @maydiscard +macro void? @each_row(InStream stream, String separator = ",", int max_rows = int::max; @body(String[] row)) @maydiscard { while (max_rows--) { @@ -86,7 +86,7 @@ macro void? @each_row(InStream stream, String separator = ",", int max_rows = in } } -macro void? CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard +macro void? CsvReader.@each_row(self, int rows = int::max; @body(String[] row)) @maydiscard { return @each_row(self.stream, self.separator, rows; row) { diff --git a/test/stdlib/src/encoding/hex.c3 b/test/stdlib/src/encoding/hex.c3 index 8319b91..fc0d0de 100644 --- a/test/stdlib/src/encoding/hex.c3 +++ b/test/stdlib/src/encoding/hex.c3 @@ -3,26 +3,26 @@ import std::encoding @norecurse; // The implementation is based on https://www.rfc-editor.org/rfc/rfc4648 -fn String encode_buffer(char[] code, char[] buffer) +fn String encode_into(char[] dst, char[] src) { - return (String)buffer[:encode_bytes(code, buffer)]; + return (String)dst[:encode_bytes_into(dst, src)]; } -fn char[]? decode_buffer(char[] code, char[] buffer) +fn char[]? decode_into(char[] dst, char[] src) { - return buffer[:decode_bytes(code, buffer)!]; + return dst[:decode_bytes_into(dst, src)!]; } fn String encode(Allocator allocator, char[] code) { - char[] data = allocator::alloc_array(allocator, char, encode_len(code.len)); - return (String)data[:encode_bytes(code, data)]; + char[] data = alloc::alloc_array(allocator, char, encode_len(code.len)); + return (String)data[:encode_bytes_into(data, code)]; } fn char[]? decode(Allocator allocator, char[] code) { - char[] data = allocator::alloc_array(allocator, char, decode_len(code.len)); - return data[:decode_bytes(code, data)!]; + char[] data = alloc::alloc_array(allocator, char, decode_len(code.len)); + return data[:decode_bytes_into(data, code)!]; } fn String tencode(char[] code) @inline => encode(tmem, code); @@ -34,18 +34,18 @@ fn char[]? tdecode(char[] code) @inline => decode(tmem, code); @param n : "Size of the input to be encoded." @return "The size of the input once encoded." *> -fn usz encode_len(usz n) => n * 2; +fn sz encode_len(sz n) => n * 2; <* Encode the content of src into dst, which must be properly sized. - @param src : "The input to be encoded." @param dst : "The encoded input." + @param src : "The input to be encoded." @return "The encoded size." @require dst.len >= encode_len(src.len) : "Destination array is not large enough" *> -fn usz encode_bytes(char[] src, char[] dst) +fn sz encode_bytes_into(char[] dst, char[] src) { - usz j = 0; + sz j = 0; foreach (v : src) { dst[j] = HEXALPHABET[v >> 4]; @@ -60,7 +60,8 @@ fn usz encode_bytes(char[] src, char[] dst) @param n : "Size of the input to be decoded." @return "The size of the input once decoded." *> -macro usz decode_len(usz n) => n / 2; +macro sz decode_len(sz n) => n / 2; + <* Decodes src into bytes. Returns the actual number of bytes written to dst. @@ -68,16 +69,16 @@ macro usz decode_len(usz n) => n / 2; Expects that src only contains hexadecimal characters and that src has even length. - @param src : "The input to be decoded." @param dst : "The decoded input." + @param src : "The input to be decoded." @require src.len % 2 == 0 : "src is not of even length" @require dst.len >= decode_len(src.len) : "Destination array is not large enough" @return? encoding::INVALID_CHARACTER *> -fn usz? decode_bytes(char[] src, char[] dst) +fn sz? decode_bytes_into(char[] dst, char[] src) { - usz i; - for (usz j = 1; j < src.len; j += 2) + sz i; + for (sz j = 1; j < src.len; j += 2) { char a = HEXREVERSE[src[j - 1]]; char b = HEXREVERSE[src[j]]; diff --git a/test/stdlib/src/encoding/ini.c3 b/test/stdlib/src/encoding/ini.c3 new file mode 100644 index 0000000..843b0ef --- /dev/null +++ b/test/stdlib/src/encoding/ini.c3 @@ -0,0 +1,236 @@ +module std::encoding::ini; +import std::io, std::collections::object; + +// Constant for the implicit section that holds keys before any section header. +// Pass ini::GLOBAL as the section argument to get/set for those keys. +const String GLOBAL = ""; + +faultdef INVALID_SECTION, INVALID_KEY, DUPLICATE_KEY; + +enum DuplicateKeyBehavior +{ + OVERWRITE, + ERROR, +} + + + +// --------------------------------------------------------------------------- +// Parsing +// --------------------------------------------------------------------------- + +<* + Parse an INI-formatted stream into an Object. + + The returned Object is a map whose keys are section names (or ini::GLOBAL + for keys that appear before any section header). Each value is itself an + Object map from key strings to string Objects. + + Call obj.free() when done, or use a temp/arena allocator. + @param [&inout] allocator + @param stream : "The stream to read from" + @param delimiter : "Key/value separator: '=' or ':' (default: '=')" + @param trim_values : "Strip surrounding whitespace from values (default: true)" + @param allow_global_keys : "Keys before the first section (default: true)" + @param allow_hash_comments : "Treat # as a comment char (default: true)" + @param allow_inline_comments : "Strip trailing inline comments from values (default: false)." + @param on_duplicate_keys : "What to do on duplicate keys (default: OVERWRITE)" + + @return "The data as an Object" + @return? INVALID_SECTION : "If a section is invalid" + @return? INVALID_KEY : "If a key is invalid" + @return? DUPLICATE_KEY : "If a key appears more than once" +*> +fn Object*? load( + Allocator allocator, InStream stream, + char delimiter = '=', bool trim_values = true, bool allow_global_keys = true, + bool allow_hash_comments = true, bool allow_inline_comments = false, + DuplicateKeyBehavior on_duplicate_keys = OVERWRITE) => @pool() +{ + Object* ini = object::new_obj(allocator); + defer catch ini.free(); + + // Only create the GLOBAL section when global keys are actually allowed. + if (allow_global_keys) ini.set(GLOBAL, object::new_obj(allocator)); + + String current_section = GLOBAL; + + // Split on "\n"; trim() on each line also strips \r for Windows \r\n files. + while (try line = io::treadline(stream)) + { + String s = line.trim(); + if (!s.len) continue; + switch (s[0]) + { + case ';': + continue; + case '#': + if (allow_hash_comments) continue; + case '[': + // Section header: [name] + if (s[^1] != ']') return INVALID_SECTION~; + String name = s[1..^2].trim(); + // Reject [] — empty section name would silently alias GLOBAL. + if (!name.len) return INVALID_SECTION~; + current_section = name; + if (!ini.has_key(name)) ini.set(name, object::new_obj(allocator)); + continue; + default: + break; + } + // Key/value pair + sz? delimiter_index = s.index_of_char(delimiter); + if (catch delimiter_index) return INVALID_KEY~; + + if (!allow_global_keys && current_section == GLOBAL) return INVALID_KEY~; + + String key = s[:delimiter_index].trim(); + + // Extract raw value, strip inline comments if enabled, then optionally trim. + String raw = s[delimiter_index + 1..]; + if (allow_inline_comments) + { + if (try p = raw.index_of(" ;")) + { + raw = raw[:p]; + } + else if (allow_hash_comments) + { + if (try p = raw.index_of(" #")) raw = raw[:p]; + } + } + String value = trim_values ? raw.trim() : raw; + + // ini.has_key(current_section) is guaranteed by the section setup above. + Object* sec = ini.get(current_section)!!; + + if (on_duplicate_keys == ERROR && sec.has_key(key)) return DUPLICATE_KEY~; + + sec.set(key, value); + } + + return ini; +} + +fn Object*? load_temp( + InStream stream, + char delimiter = '=', bool trim_values = true, bool allow_global_keys = true, + bool allow_hash_comments = true, bool allow_inline_comments = false, + DuplicateKeyBehavior on_duplicate_keys = OVERWRITE) +{ + return load(tmem, stream, delimiter, trim_values, allow_global_keys, allow_hash_comments, allow_inline_comments, on_duplicate_keys); +} + +<* + Parse an INI-formatted stream into an Object. + + The returned Object is a map whose keys are section names (or ini::GLOBAL + for keys that appear before any section header). Each value is itself an + Object map from key strings to string Objects. + + Call obj.free() when done, or use a temp/arena allocator. + @param [&inout] allocator + @param string : "The string to read from" + @param delimiter : "Key/value separator: '=' or ':' (default: '=')" + @param trim_values : "Strip surrounding whitespace from values (default: true)" + @param allow_global_keys : "Keys before the first section (default: true)" + @param allow_hash_comments : "Treat # as a comment char (default: true)" + @param allow_inline_comments : "Strip trailing inline comments from values (default: false)." + @param on_duplicate_keys : "What to do on duplicate keys (default: OVERWRITE)" + + @return "The data as an Object" + @return? INVALID_SECTION : "If a section is invalid" + @return? INVALID_KEY : "If a key is invalid" + @return? DUPLICATE_KEY : "If a key appears more than once" +*> +fn Object*? parse( + Allocator allocator, String string, + char delimiter = '=', bool trim_values = true, bool allow_global_keys = true, + bool allow_hash_comments = true, bool allow_inline_comments = false, + DuplicateKeyBehavior on_duplicate_keys = OVERWRITE) => @pool() +{ + ByteReader reader; + reader.init(string); + return load(allocator, &reader, delimiter, trim_values, allow_global_keys, allow_hash_comments, allow_inline_comments, on_duplicate_keys); +} + +fn Object*? tparse( + String string, + char delimiter = '=', bool trim_values = true, bool allow_global_keys = true, + bool allow_hash_comments = true, bool allow_inline_comments = false, + DuplicateKeyBehavior on_duplicate_keys = OVERWRITE) +{ + return parse(tmem, string, delimiter, trim_values, allow_global_keys, allow_hash_comments, allow_inline_comments, on_duplicate_keys); +} + +// --------------------------------------------------------------------------- +// Serialization +// --------------------------------------------------------------------------- + +<* + Serialize an INI Object back to INI text. + + Expects obj to be a map of section-name -> Object map of key -> string Object, + as produced by parse(). Section order is not guaranteed. + @param [&inout] allocator : "The allocator to use for allocating the resulting string" + @param delimiter : "Key/value separator: '=' or ':' (default: '=')" + @param pad_delimiter : "Surround delimiter with spaces when encoding: 'k = v' vs 'k=v' (default: true)" + + @return "The encoded string" +*> +fn String encode(Allocator allocator, Object* obj, char delimiter = '=', bool pad_delimiter = true) => @pool() +{ + DString d; + save(&d, obj, delimiter, pad_delimiter)!!; + return d.copy_str(allocator); +} + +<* + Serialize an INI Object back to INI text. + + Expects obj to be a map of section-name -> Object map of key -> string Object, + as produced by parse(). Section order is not guaranteed. + @param stream : "The stream to write to" + @param delimiter : "Key/value separator: '=' or ':' (default: '=')" + @param pad_delimiter : "Surround delimiter with spaces when encoding: 'k = v' vs 'k=v' (default: true)" + + @return "The encoded string" +*> +fn long? save(OutStream stream, Object* obj, char delimiter = '=', bool pad_delimiter = true) +{ + String fmt = pad_delimiter ? "%s %c %s" : "%s%c%s"; + + long len = 0; + // Global keys first (if any) + if (try Object* global_sec = obj.get(GLOBAL) && global_sec.is_map()) + { + global_sec.@each_entry(; String key, Object* val) + { + len += io::fprintfn(stream, fmt, key, delimiter, val.s)!; + }; + len += io::fprintn(stream)!; + } + + // Named sections + obj.@each_entry(; String section, Object* sec) + { + if (section != GLOBAL) + { + len += io::fprintfn(stream, "[%s]", section)!; + sec.@each_entry(; String key, Object* val) + { + len += io::fprintfn(stream, fmt, key, delimiter, val.s)!; + }; + len += io::fprintn(stream)!; + } + }; + return len; +} + +fn String tencode(Object* obj, char delimiter = '=', bool pad_delimiter = true) +{ + DString d; + d.tinit(512); + save(&d, obj, delimiter, pad_delimiter)!!; + return d.str_view(); +} diff --git a/test/stdlib/src/encoding/json.c3 b/test/stdlib/src/encoding/json.c3 index ce99c0f..e80e941 100644 --- a/test/stdlib/src/encoding/json.c3 +++ b/test/stdlib/src/encoding/json.c3 @@ -9,21 +9,26 @@ faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPT int max_depth = 128; -fn Object*? parse_string(Allocator allocator, String s) +enum JsonFlavor { - return parse(allocator, (ByteReader){}.init(s)); + JSON, + JSONC +} +fn Object*? parse(Allocator allocator, String s, JsonFlavor flavor = JSONC) +{ + return load(allocator, (ByteReader){}.init(s), flavor); } -fn Object*? tparse_string(String s) +fn Object*? tparse(String s, JsonFlavor flavor = JSONC) { - return parse(tmem, (ByteReader){}.init(s)); + return load(tmem, (ByteReader){}.init(s)); } -fn Object*? parse(Allocator allocator, InStream s) +fn Object*? load(Allocator allocator, InStream s, JsonFlavor flavor = JSONC) { @stack_mem(512; Allocator smem) { - JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator }; + JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator, .flavor = flavor }; @pool() { return parse_any(&context)!; @@ -31,9 +36,9 @@ fn Object*? parse(Allocator allocator, InStream s) }; } -fn Object*? tparse(InStream s) +fn Object*? temp_load(InStream s, JsonFlavor flavor = JSONC) { - return parse(tmem, s); + return load(tmem, s, flavor); } // -- Implementation follows -- @@ -57,21 +62,17 @@ enum JsonTokenType @local struct JsonContext @local { - uint line; InStream stream; Allocator allocator; JsonTokenType token; DString last_string; double last_number; - char current; int depth; - bitstruct : char - { - bool skip_comments; - bool reached_end; - bool pushed_back; - } - + int line; + char current; + bool reached_end; + bool pushed_back; + JsonFlavor flavor; } @@ -86,8 +87,8 @@ fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local case RBRACE: case RBRACKET: case COLON: return UNEXPECTED_CHARACTER~; - case STRING: return object::new_string(context.last_string.str_view(), context.allocator); - case NUMBER: return object::new_float(context.last_number, context.allocator); + case STRING: return object::new_string(context.allocator, context.last_string.str_view()); + case NUMBER: return object::new_float(context.allocator, context.last_number); case TRUE: return object::new_bool(true); case FALSE: return object::new_bool(false); case NULL: return object::new_null(); @@ -165,9 +166,9 @@ fn Object*? parse_map(JsonContext* context) @local @stack_mem(256; Allocator mem) { DString temp_key = dstring::new_with_capacity(mem, 32); - while (token != JsonTokenType.RBRACE) + while (token != RBRACE) { - if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER~; + if (token != STRING) return UNEXPECTED_CHARACTER~; DString string = context.last_string; // Copy the key to our temp holder, since our // last_string may be used in parse_any @@ -177,12 +178,13 @@ fn Object*? parse_map(JsonContext* context) @local Object* element = parse_any(context)!; map.set(temp_key.str_view(), element); token = advance(context)!; - if (token == JsonTokenType.COMMA) + if (token == COMMA) { token = advance(context)!; + if (token == RBRACE && context.flavor == JSON) return UNEXPECTED_CHARACTER~; continue; } - if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER~; + if (token != RBRACE) return UNEXPECTED_CHARACTER~; } return map; }; @@ -195,17 +197,18 @@ fn Object*? parse_array(JsonContext* context) @local defer context.depth--; if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED~; JsonTokenType token = advance(context)!; - while (token != JsonTokenType.RBRACKET) + while (token != RBRACKET) { Object* element = parse_from_token(context, token)!; list.push(element); token = advance(context)!; - if (token == JsonTokenType.COMMA) + if (token == COMMA) { token = advance(context)!; + if (token == RBRACKET && context.flavor == JSON) return UNEXPECTED_CHARACTER~; continue; } - if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER~; + if (token != RBRACKET) return UNEXPECTED_CHARACTER~; } return list; } @@ -262,8 +265,21 @@ fn JsonTokenType? advance(JsonContext* context) @local case '\v': continue; case '/': - if (!context.skip_comments) break WS; + if (context.flavor == JSON) break WS; c = read_next(context)!; + if (c == '/') + { + while (try ch = read_next(context)) + { + if (ch == '\n') + { + context.line++; + pushback(context, ch); + continue WS; + } + } + break WS; + } if (c != '*') { pushback(context, c); @@ -386,7 +402,7 @@ fn JsonTokenType? lex_string(JsonContext* context) { c = read_next(context)!; if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE~; - val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0'); + val = val << 4u + (c > '9' ? (c | 32u) - 'a' + 10u : c - '0'); } context.last_string.append_char32(val); continue; diff --git a/test/stdlib/src/encoding/json_marshal.c3 b/test/stdlib/src/encoding/json_marshal.c3 index a49cbe2..7b3143f 100644 --- a/test/stdlib/src/encoding/json_marshal.c3 +++ b/test/stdlib/src/encoding/json_marshal.c3 @@ -13,10 +13,10 @@ import std::io; macro bool may_serialize($Type) @const { - $switch $Type.kindof: + $switch $Type::kind: $case STRUCT: - $foreach $member : $Type.membersof: - $if $member.nameof == "" ||| !may_serialize($member.type): + $foreach $member : $Type::members: + $if $member.name == "" ||| !may_serialize($member.type): return false; $endif $endforeach @@ -33,7 +33,7 @@ macro bool may_serialize($Type) @const return true; $case TYPEDEF: $case CONSTDEF: - return may_serialize($Type.inner); + return may_serialize($Type::inner); $default: return false; $endswitch @@ -43,7 +43,7 @@ macro bool may_serialize($Type) @const @param allocator: "The allocator to use for the result" @param value: "The struct value to marshal" - @require $kindof(value) == STRUCT + @require @kindof(value) == STRUCT @require may_serialize($typeof(value)) : "Expected a serializable type" @return "The JSON string representation" *> @@ -60,7 +60,7 @@ macro String marshal(Allocator allocator, value) => @pool() @param stream: "The steam to serialize to" @param value: "The struct value to marshal" - @require $kindof(value) == STRUCT + @require @kindof(value) == STRUCT @require may_serialize($typeof(value)) : "Expected a serializable type" *> macro void? marshal_to(OutStream stream, value) @@ -76,7 +76,7 @@ fn void? serialize(OutStream stream, Type value) stream.write_byte('{')!; var $first = true; - $foreach $i, $member : Type.membersof: + $foreach $i, $member : Type::members: $if $i > 0: stream.write_byte(',')!; $endif @@ -84,7 +84,7 @@ fn void? serialize(OutStream stream, Type value) // Add field name (always quoted) stream.write_byte('"')!; - stream.write($member.nameof)!; + stream.write($member.name)!; stream.write(`":`)!; serialize_value{$member.type}(stream, $member.get(value))!; @@ -99,7 +99,7 @@ fn void? serialize(OutStream stream, Type value) *> fn void? serialize_value(OutStream stream, TypeElement value) { - $switch TypeElement.kindof: + $switch TypeElement::kind: $case STRUCT: return serialize{TypeElement}(stream, value); $case ARRAY: @@ -116,19 +116,19 @@ fn void? serialize_value(OutStream stream, TypeElement value) stream.write(value ? "true" : "false")!; $case CONSTDEF: $case TYPEDEF: - $switch (TypeElement.typeid): + $switch (TypeElement::typeid): $case String: @pool() { stream.write(value.tescape(false))!; }; $default: - var $Inner = $typefrom(TypeElement.inner); + var $Inner = $typefrom(TypeElement::inner); return serialize_value{$Inner}(stream, ($Inner)value); $endswitch $case ENUM: stream.write_byte('"')!; - stream.write(value.nameof)!; + stream.write(value.description)!; stream.write_byte('"')!; $default: - $if TypeElement.typeid == String.typeid: + $if TypeElement::typeid == String::typeid: @pool() { stream.write(value.tescape(false))!; }; $else $error "Unsupported type"; diff --git a/test/stdlib/src/encoding/pem.c3 b/test/stdlib/src/encoding/pem.c3 index 0406a6d..558f061 100644 --- a/test/stdlib/src/encoding/pem.c3 +++ b/test/stdlib/src/encoding/pem.c3 @@ -82,9 +82,9 @@ fn Pem create(Allocator allocator, char[] data, String tag, PemHeader... args) Pem result = { .allocator = allocator, .tag = tag.copy(allocator), - .data = allocator::clone_slice(allocator, data), + .data = alloc::clone_slice(allocator, data), }; - result.headers.init(allocator, capacity: max(args.len, 16)); + result.headers.init(allocator, capacity: max((int)args.len, 16)); foreach (arg : args) { result.add_header(arg[0], arg[1]); @@ -118,11 +118,11 @@ fn void Pem.free(&self) { self.headers.@each(;String key, String value) { - allocator::free(self.allocator, value); + alloc::free(self.allocator, value); }; self.headers.free(); self.tag.free(self.allocator); - allocator::free(self.allocator, self.data); + alloc::free(self.allocator, self.data); } mem::zero_volatile(@as_char_view(*self)); } @@ -188,7 +188,7 @@ fn Pem? _decode_single(Allocator allocator, String[]* lines_io) @local // The Post-Encapsulation-Boundary is the same, but uses "END", and the extracted tag must match. // Since the input might contain more than one PEM unit, we need to search for the ending encapsulation boundary dynamically. String post_eb; - usz endl; + sz endl; for SEARCH_EB: (endl = 1; endl < lines.len; endl++) { if (lines[endl].len > POST_EB_PREFIX.len && lines[endl][0:EB_DELIMITER.len] == EB_DELIMITER) @@ -211,7 +211,7 @@ fn Pem? _decode_single(Allocator allocator, String[]* lines_io) @local { if (!HEADER_KEY_SET.contains(lines[0][0])) return INVALID_HEADER~; // not a multiline header? error out if the first char is not appropriate String[] marker = lines; // temporary marker - usz span = 1; // how many lines this header spans + sz span = 1; // how many lines this header spans // Search for multi-line key-value pairs, indicated by a whitespace character beginning the current line. for (lines = lines[1..]; lines[0].len > 0 && ascii::WHITESPACE_SET.contains(lines[0][0]); lines = lines[1..], span++); @@ -273,7 +273,7 @@ fn String? encode_pem(Pem pem, Allocator allocator, bool use_crlf = false) if (!key.len) return HEADER_KEY_REQUIRED~; String value = pem.headers[key]!!; if (!value.len) return HEADER_VALUE_REQUIRED~; - usz first_line_length = 64 - 2 - key.len; + sz first_line_length = 64 - 2 - key.len; if (value.len <= first_line_length) { out.appendf("%s: %s%s", key, value, line_ending); @@ -326,7 +326,7 @@ fn String? encode(Allocator allocator, char[] data, String tag, PemHeader... hea *> fn String? encode_many(Allocator allocator, char[][] bodies, String[] tags, PemHeader[]... pem_headers, bool use_crlf = false) { - usz entries = max(bodies.len, tags.len, pem_headers.len); + sz entries = max(bodies.len, tags.len, pem_headers.len); switch { case bodies.len < entries: return BODY_REQUIRED~; diff --git a/test/stdlib/src/hash/a5hash.c3 b/test/stdlib/src/hash/a5hash.c3 index c353a4a..85575ff 100644 --- a/test/stdlib/src/hash/a5hash.c3 +++ b/test/stdlib/src/hash/a5hash.c3 @@ -43,8 +43,8 @@ macro void @a5mul(#u, #v, #lo, #hi) @local fn ulong hash(char[] data, ulong seed = 0) { - ulong seed1 = 0x243F_6A88_85A3_08D3 ^ data.len; - ulong seed2 = 0x4528_21E6_38D0_1377 ^ data.len; + ulong seed1 = (ulong)(0x243F_6A88_85A3_08D3 ^ data.len); + ulong seed2 = (ulong)(0x4528_21E6_38D0_1377 ^ data.len); ulong val10 = 0xAAAA_AAAA_AAAA_AAAA; ulong val01 = 0x5555_5555_5555_5555; ulong a, b; @@ -85,7 +85,7 @@ fn ulong hash(char[] data, ulong seed = 0) } else { - a = data.len ? (data[0] | (data.len > 1 ? ((ulong)data[1] << 8) : 0) | (data.len > 2 ? ((ulong)data[2] << 16) : 0)) : 0; + a = data.len ? (data[0] | (data.len > 1 ? ((ulong)data[1] << 8) : 0u) | (data.len > 2 ? ((ulong)data[2] << 16) : 0u)) : 0u; b = 0; } diff --git a/test/stdlib/src/hash/adler32.c3 b/test/stdlib/src/hash/adler32.c3 index 5a88c09..7227045 100644 --- a/test/stdlib/src/hash/adler32.c3 +++ b/test/stdlib/src/hash/adler32.c3 @@ -18,7 +18,7 @@ fn void Adler32.init(&self) *self = { 1, 0 }; } -fn void Adler32.updatec(&self, char c) +fn void Adler32.update_byte(&self, char c) { self.a = (self.a + c) % ADLER32_CONST; self.b = (self.b + self.a) % ADLER32_CONST; @@ -38,7 +38,7 @@ fn void Adler32.update(&self, char[] data) uint b = self.b; char* buf = data; - usz len = data.len; + sz len = data.len; // Align pointer traversing buffer pointer to the unroll alignment size. if (len % UNROLL_SIZE != 0) diff --git a/test/stdlib/src/hash/blake2.c3 b/test/stdlib/src/hash/blake2.c3 index edc5e5c..036c654 100644 --- a/test/stdlib/src/hash/blake2.c3 +++ b/test/stdlib/src/hash/blake2.c3 @@ -24,36 +24,35 @@ const SIZE_384 = 48; const SIZE_512 = 64; -macro @g(r, m, $s, i, #a, #b, #c, #d) @local +fn void _g(sz r, NumType[] m, char[16][] s, sz i, NumType* a, NumType* b, NumType* c, NumType* d) @local @inline { - // I really dislike using these conditional one-liners, but... it's the only difference between 2s and 2b besides buffer sizes (mainly). - #a += #b + m[$s[r][2*i+0]]; - #d = (#d ^ #a).rotr($sizeof(#a) == ulong.sizeof ??? 32 : 16); - #c += #d; - #b = (#b ^ #c).rotr($sizeof(#a) == ulong.sizeof ??? 24 : 12); - #a += #b + m[$s[r][2*i+1]]; - #d = (#d ^ #a).rotr($sizeof(#a) == ulong.sizeof ??? 16 : 8); - #c += #d; - #b = (#b ^ #c).rotr($sizeof(#a) == ulong.sizeof ??? 63 : 7); + *a += *b + m[s[r][2*i+0]]; + *d = (*d ^ *a).rotr(NumType == ulong ??? 32 : 16); + *c += *d; + *b = (*b ^ *c).rotr(NumType == ulong ??? 24 : 12); + *a += *b + m[s[r][2*i+1]]; + *d = (*d ^ *a).rotr(NumType == ulong ??? 16 : 8); + *c += *d; + *b = (*b ^ *c).rotr(NumType == ulong ??? 63 : 7); } -macro @round(r, m, $s, #v) @local +fn void _round(sz r, NumType[] m, char[16][] s, NumType[] v) @local @inline { - @g(r, m, $s, 0, #v[ 0], #v[ 4], #v[ 8], #v[12]); - @g(r, m, $s, 1, #v[ 1], #v[ 5], #v[ 9], #v[13]); - @g(r, m, $s, 2, #v[ 2], #v[ 6], #v[10], #v[14]); - @g(r, m, $s, 3, #v[ 3], #v[ 7], #v[11], #v[15]); - @g(r, m, $s, 4, #v[ 0], #v[ 5], #v[10], #v[15]); - @g(r, m, $s, 5, #v[ 1], #v[ 6], #v[11], #v[12]); - @g(r, m, $s, 6, #v[ 2], #v[ 7], #v[ 8], #v[13]); - @g(r, m, $s, 7, #v[ 3], #v[ 4], #v[ 9], #v[14]); + _g(r, m, s, 0, &v[ 0], &v[ 4], &v[ 8], &v[12]); + _g(r, m, s, 1, &v[ 1], &v[ 5], &v[ 9], &v[13]); + _g(r, m, s, 2, &v[ 2], &v[ 6], &v[10], &v[14]); + _g(r, m, s, 3, &v[ 3], &v[ 7], &v[11], &v[15]); + _g(r, m, s, 4, &v[ 0], &v[ 5], &v[10], &v[15]); + _g(r, m, s, 5, &v[ 1], &v[ 6], &v[11], &v[12]); + _g(r, m, s, 6, &v[ 2], &v[ 7], &v[ 8], &v[13]); + _g(r, m, s, 7, &v[ 3], &v[ 4], &v[ 9], &v[14]); } macro common_compress(instance, $rounds, $iv, $sigma, block) @local { $typeof(instance.h[0])[16] m, v; - ((char*)&m)[:$sizeof(block)] = block[..]; + ((char*)&m)[:@sizeof(block)] = block[..]; v[:8] = instance.h[..]; v[ 8] = $iv[0]; @@ -65,22 +64,22 @@ macro common_compress(instance, $rounds, $iv, $sigma, block) @local v[14] = $iv[6] ^ instance.f[0]; v[15] = $iv[7] ^ instance.f[1]; - $for usz $i = 0; $i < $rounds; $i++: - @round($i, m, $sigma, v); + $for sz $i = 0; $i < $rounds; $i++: + _round{$typeof(instance.h[0])}($i, m[..], $sigma[..], v[..]); $endfor - $for usz $i = 0; $i < 8; $i++: + $for sz $i = 0; $i < 8; $i++: instance.h[$i] ^= v[$i] ^ v[$i + 8]; $endfor } -macro _add_ctr(instance, usz amount) @local +macro _add_ctr(instance, sz amount) @local { instance.t[0] += ($typeof(instance.t[0]))amount; instance.t[1] += ($typeof(instance.t[0]))(instance.t[0] < amount); // adds 1 on overflow of [0] } -macro common_init(instance, $ParamType, $iv, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) @local +macro common_init(instance, $ParamType, $iv, sz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) @local { mem::zero_volatile(@as_char_view(*instance)); // explicitly because habits around hash init usually involve @noinit @@ -96,11 +95,11 @@ macro common_init(instance, $ParamType, $iv, usz out_len, char[] key = {}, char[ if (salt.len) p.salt[:salt.len] = salt[..]; if (personal.len) p.personal[:personal.len] = personal[..]; - array::@zip_into(((char*)&instance.h)[:$sizeof(p)], ((char*)&p)[:$sizeof(p)], fn (a, b) => a ^ b); // bytes(self.h) ^= bytes(p) + array::@zip_into(((char*)&instance.h)[:@sizeof(p)], ((char*)&p)[:@sizeof(p)], fn (a, b) => a ^ b); // bytes(self.h) ^= bytes(p) - if (key.len) + if (key.len) { - char[$sizeof($iv[0])*16] dummy = {}; + char[@sizeof($iv[0])*16] dummy = {}; dummy[:key.len] = key[..]; instance.update(dummy[..]); // consume a FULL block mem::zero_volatile(dummy[..]); // do not optimize clearing this from the stack @@ -111,7 +110,7 @@ macro common_update(instance, $block_size, char[] data) @local { if (@unlikely(!data.len)) return; - usz fill = $block_size - instance.buflen; + sz fill = $block_size - instance.buflen; if (data.len > fill) { instance.buf[instance.buflen:fill] = data[:fill]; @@ -132,15 +131,16 @@ macro common_update(instance, $block_size, char[] data) @local instance.buflen += data.len; } -macro common_final(instance, $output_length) @local -{ - char[$output_length] result = {}; - if ($output_length != instance.outlen) return result; +<* + @param [out] into : "The destination for the resultant hash. Its length must be at least the size of the hash's initialized size." + @require into.len >= instance.outlen : "The result slice must be large enough to accept the full hash digest." +*> +macro common_final(instance, char[] into) @local +{ _add_ctr(instance, instance.buflen); - if (instance.f[0]) return result; // technically an error return - var $max = $typeof(instance.h[0]).max; + var $max = $typeof(instance.h[0])::max; if (instance.last_node) instance.f[1] = $max; instance.f[0] = $max; @@ -149,8 +149,7 @@ macro common_final(instance, $output_length) @local defer mem::zero_volatile(@as_char_view(*instance)); // destroy the current context implicitly - result[:instance.outlen] = @as_char_view(instance.h)[:instance.outlen]; - return result; + into[:instance.outlen] = @as_char_view(instance.h)[:instance.outlen]; } @@ -187,8 +186,8 @@ struct Blake2b ulong[2] t; ulong[2] f; char[BLAKE2B_BLOCKBYTES] buf; - usz buflen; - usz outlen; + sz buflen; + sz outlen; char last_node; } @@ -213,10 +212,12 @@ struct Blake2bParam @packed @local *> macro blake2b_hash($out_len, data, char[] key = {}, char[] salt = {}) { + char[$out_len] result @noinit; Blake2b b @noinit; b.init($out_len, key, salt); b.update(data[..]); - return b.final($out_len); + b.final(result[..]); + return result; } alias b = blake2b_hash; @@ -232,7 +233,7 @@ alias b_384 = blake2b_384; alias b_512 = blake2b_512; <* - Blake2b initialization method. Presents various options + Blake2b initialization method. Presents various options for customizing the hash output. @param out_len : "The desired output length from the hash function." @param[in] key : "An optional key value to use (keys the entire hash value to give HMAC-like functionality)." @@ -244,7 +245,7 @@ alias b_512 = blake2b_512; @require !salt.ptr || (salt.len > 0 && salt.len <= BLAKE2B_SALTBYTES) : "A specified salt's length must be within the proper range." @require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range." *> -fn void Blake2b.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) +fn void Blake2b.init(&self, sz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) => common_init(self, Blake2bParam, BLAKE2B_IV, out_len, key, salt, personal); <* @@ -264,16 +265,12 @@ fn void Blake2b.update(&self, char[] data) <* Finalize the hash context and return the hash result at the given size. - @param $output_length : "The length of the output array which is returned by value, instead of as a slice." + @param [out] into : "Storage buffer for the hash's result. Must have a length >= the size given to the hash context at init." - @require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size." + @require into.len >= self.outlen : "The specified output slice length MUST be equal to or larger than the initialized output size." *> -macro char[*] Blake2b.final(&self, $output_length) => _blake2b_final{$output_length}(self); +macro void Blake2b.final(&self, char[] into) => common_final(self, into); -fn char[OUTPUT_LENGTH] _blake2b_final(Blake2b* self) @local -{ - return common_final(self, OUTPUT_LENGTH); -} // ====================================================================================== // BEGIN Blake2s contents. Do not separate this from Blake2b: there's not really a point. @@ -303,8 +300,8 @@ struct Blake2s uint[2] t; uint[2] f; char[BLAKE2S_BLOCKBYTES] buf; - usz buflen; - usz outlen; + sz buflen; + sz outlen; char last_node; } @@ -328,10 +325,12 @@ struct Blake2sParam @packed @local *> macro blake2s_hash($out_len, data, char[] key = {}, char[] salt = {}) { + char[$out_len] result @noinit; Blake2s b @noinit; b.init($out_len, key, salt); b.update(data[..]); - return b.final($out_len); + b.final(result[..]); + return result; } alias s = blake2s_hash; @@ -347,7 +346,7 @@ alias s_224 = blake2s_224; alias s_256 = blake2s_256; <* - Blake2s initialization method. Presents various options + Blake2s initialization method. Presents various options @param out_len : "The desired output length from the hash function." @param[in] key : "An optional key value to use (keys the entire hash value to give HMAC-like functionality)." @@ -359,7 +358,7 @@ alias s_256 = blake2s_256; @require !salt.ptr || (salt.len > 0 && salt.len <= BLAKE2S_SALTBYTES) : "A specified salt's length must be within the proper range." @require !personal.ptr || (personal.len > 0 && personal.len <= BLAKE2B_PERSONALBYTES) : "A specified personalization's length must be within the proper range." *> -fn void Blake2s.init(&self, usz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) +fn void Blake2s.init(&self, sz out_len, char[] key = {}, char[] salt = {}, char[] personal = {}) => common_init(self, Blake2sParam, BLAKE2S_IV, out_len, key, salt, personal); <* @@ -379,9 +378,8 @@ fn void Blake2s.update(&self, char[] data) <* Finalize the hash context and return the hash result at the given size. - @param $output_length : "The length of the output array which is returned by value, instead of as a slice." + @param [out] into : "Storage buffer for the hash's result. Must have a length >= the size given to the hash context at init." - @require $output_length == self.outlen : "The specified compile-time output size MUST be equal to the initialized output size." + @require into.len >= self.outlen : "The specified output slice length MUST be equal to or larger than the initialized output size." *> -macro char[*] Blake2s.final(&self, $output_length) - => common_final(self, $output_length); +macro void Blake2s.final(&self, char[] into) => common_final(self, into); diff --git a/test/stdlib/src/hash/blake3.c3 b/test/stdlib/src/hash/blake3.c3 index 0d50085..a45fc07 100644 --- a/test/stdlib/src/hash/blake3.c3 +++ b/test/stdlib/src/hash/blake3.c3 @@ -11,7 +11,7 @@ import std::thread; const BLOCK_SIZE = 64; const CHUNK_SIZE = 1024; const KEY_SIZE = 32; -const KEY_SIZE_WORDS = KEY_SIZE / uint.sizeof; +const KEY_SIZE_WORDS = KEY_SIZE / uint::size; const OUT_SIZE = 32; const MAX_DEPTH = 54; @@ -101,7 +101,7 @@ constdef Blake3Flags : inline char struct Blake3ChunkState @local { uint[8] cv; - ulong chunk_counter; + long chunk_counter; char[BLOCK_SIZE] buf; char buf_len; char blocks_compressed; @@ -111,7 +111,7 @@ struct Blake3ChunkState @local struct Blake3Output @local { uint[KEY_SIZE_WORDS] input_cv; - ulong counter; + long counter; char[BLOCK_SIZE] block; char block_len; char flags; @@ -155,7 +155,7 @@ struct Blake3 @require !key.len || key.len == KEY_SIZE : "Key value must be empty or exactly 32 bytes." @require $out_size > 0 : "You cannot use a zero $out_size." *> -macro char[*] hash(char[] data, char[] key = {}, usz seek = 0, usz $out_size = 32) +macro char[*] hash(char[] data, char[] key = {}, sz seek = 0, sz $out_size = 32) { char[$out_size] result; Blake3 b @noinit; @@ -181,7 +181,7 @@ macro char[*] hash(char[] data, char[] key = {}, usz seek = 0, usz $out_size = 3 @require $out_size > 0 : "You cannot use a zero $out_size." *> -macro char[*] ctx_hash(char[] data, char[] context, usz seek = 0, usz $out_size = 32) +macro char[*] ctx_hash(char[] data, char[] context, sz seek = 0, sz $out_size = 32) { char[$out_size] result; Blake3 b = new_from_context(context); @@ -228,7 +228,7 @@ fn void Blake3.init(&self, char[] key = {}, char explicit_flags = 0) if (key.len) { - foreach (i, &w : self.key) *w = mem::load((uint*)&key[i * $sizeof(self.key[0])], 1); + foreach (i, &w : self.key) *w = mem::load((uint*)&key[i * @sizeof(self.key[0])], 1); if (!explicit_flags) explicit_flags = Blake3Flags.KEYED_HASH; } else @@ -251,9 +251,9 @@ fn void _reset(Blake3* self) @local @inline <* Private function to merge tree results. *> -fn void _merge_cv_stack(Blake3* self, ulong total_len) @local @inline +fn void _merge_cv_stack(Blake3* self, long total_len) @local @inline { - usz post_merge_stack_len = (usz)@popcnt(total_len); + sz post_merge_stack_len = (sz)@popcnt(total_len); for (; self.cv_stack_len > post_merge_stack_len; self.cv_stack_len--) { char* parent_node = &self.cv_stack[(self.cv_stack_len - 2) * OUT_SIZE]; @@ -265,7 +265,7 @@ fn void _merge_cv_stack(Blake3* self, ulong total_len) @local @inline <* Private function to add a new tree onto the stack. *> -fn void Blake3.push_cv(&self, char* new_cv, ulong chunk_counter) @inline +fn void Blake3.push_cv(&self, char* new_cv, long chunk_counter) @inline { _merge_cv_stack(self, chunk_counter); self.cv_stack[self.cv_stack_len * OUT_SIZE : OUT_SIZE] = new_cv[:OUT_SIZE]; @@ -284,7 +284,7 @@ fn void Blake3.update(&self, char[] input, bool use_tbb = false) if (self.chunk.len() > 0) { - usz take = min(CHUNK_SIZE - self.chunk.len(), input.len); + sz take = min(CHUNK_SIZE - self.chunk.len(), input.len); self.chunk.update(input[:take]); input = input[take..]; @@ -299,12 +299,14 @@ fn void Blake3.update(&self, char[] input, bool use_tbb = false) while (input.len > CHUNK_SIZE) { - usz subtree_len = @round_down_to_power_of_2(input.len); - ulong count_so_far = self.chunk.chunk_counter * CHUNK_SIZE; + sz subtree_len = @round_down_to_power_of_2(input.len); + long count_so_far = self.chunk.chunk_counter * CHUNK_SIZE; - while ((((ulong)(subtree_len - 1)) & count_so_far) != 0) subtree_len /= 2; - - ulong subtree_chunks = subtree_len / CHUNK_SIZE; + while (((ulong)(subtree_len - 1) & (ulong)count_so_far) != 0) + { + subtree_len /= 2; + } + long subtree_chunks = subtree_len / CHUNK_SIZE; if (subtree_len <= CHUNK_SIZE) { Blake3ChunkState chunk_state; @@ -345,7 +347,7 @@ fn void Blake3.update(&self, char[] input, bool use_tbb = false) @require into.len >= into_len : "The requested output size must be equal to or less than the size of the output slice." *> -fn void Blake3.final(&self, char[] into, usz into_len, usz seek = 0) +fn void Blake3.final(&self, char[] into, sz into_len, sz seek = 0) { if (!into_len) return; @@ -357,7 +359,7 @@ fn void Blake3.final(&self, char[] into, usz into_len, usz seek = 0) } Blake3Output o @noinit; - usz cvs_remaining; + sz cvs_remaining; if (self.chunk.len() > 0) { cvs_remaining = self.cv_stack_len; @@ -365,7 +367,7 @@ fn void Blake3.final(&self, char[] into, usz into_len, usz seek = 0) } else { - cvs_remaining = (usz)self.cv_stack_len - 2; + cvs_remaining = (sz)self.cv_stack_len - 2; o = parent_output(&self.cv_stack[cvs_remaining * KEY_SIZE], self.key[..], self.chunk.flags); } @@ -409,7 +411,7 @@ fn void Blake3ChunkState.init(&self, uint[] key, char flags) @inline @param [in] key @param chunk_counter *> -fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @inline +fn void Blake3ChunkState.reset(&self, uint[] key, long chunk_counter) @inline { self.init(key, self.flags); // maintain its own flags self.chunk_counter = chunk_counter; // update chunk counter @@ -418,17 +420,17 @@ fn void Blake3ChunkState.reset(&self, uint[] key, ulong chunk_counter) @inline <* Get bytes length of consumed data. *> -fn usz Blake3ChunkState.len(&self) @operator(len) @inline - => (BLOCK_SIZE * (usz)self.blocks_compressed) + (usz)self.buf_len; +fn sz Blake3ChunkState.len(&self) @operator(len) @inline + => (BLOCK_SIZE * (sz)self.blocks_compressed) + (sz)self.buf_len; <* Ingest an amount of bytes into the chunk's buffer. NOTE: Doesn't check for underflow. @param [in] data : "Data to ingest." *> -fn usz Blake3ChunkState.fill_buf(&self, char[] data) @inline +fn sz Blake3ChunkState.fill_buf(&self, char[] data) @inline { - usz take = min(BLOCK_SIZE - (usz)self.buf_len, data.len); + sz take = min(BLOCK_SIZE - (sz)self.buf_len, data.len); self.buf[self.buf_len:take] = data[:take]; self.buf_len += (char)take; return take; @@ -449,7 +451,7 @@ fn void Blake3ChunkState.update(&self, char[] input) { if (self.buf_len) { - usz take = self.fill_buf(input); + sz take = self.fill_buf(input); input = input[take..]; if (input.len) { @@ -481,7 +483,7 @@ fn Blake3Output Blake3ChunkState.output(&self) @inline @param counter @param flags *> -fn Blake3Output make_output(uint[] key, char* in_block, usz block_len, ulong counter, char flags) @local @noinline +fn Blake3Output make_output(uint[] key, char* in_block, sz block_len, long counter, char flags) @local @noinline { Blake3Output o; o.input_cv[..] = key[..]; @@ -521,19 +523,19 @@ macro void Blake3Output.chaining_value(&self, char* cv) @param seek @param [inout] into *> -fn void Blake3Output.root_bytes(&self, usz seek, char[] into) +fn void Blake3Output.root_bytes(&self, sz seek, char[] into) { if (!into.len) return; - ulong output_block_counter = seek / BLOCK_SIZE; - usz offset_within_block = seek % BLOCK_SIZE; + long output_block_counter = seek / BLOCK_SIZE; + long offset_within_block = seek % BLOCK_SIZE; char[BLOCK_SIZE] wide_buf; if (offset_within_block) { compress_xof(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, wide_buf[..]); - usz avail = BLOCK_SIZE - offset_within_block; - usz bytes = min(into.len, avail); + long avail = BLOCK_SIZE - offset_within_block; + sz bytes = (sz)min((long)into.len, avail); into[:bytes] = wide_buf[offset_within_block:bytes]; into = into[bytes..]; output_block_counter++; @@ -543,7 +545,7 @@ fn void Blake3Output.root_bytes(&self, usz seek, char[] into) @xof_many(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, into, into.len / BLOCK_SIZE); } output_block_counter += into.len / 64; - into = into[(usz)(into.len & -64ll) ..]; + into = into[(sz)(into.len & -64ll) ..]; if (into.len) { compress_xof(self.input_cv[..], self.block, self.block_len, output_block_counter, self.flags | Blake3Flags.ROOT, wide_buf[..]); @@ -563,10 +565,10 @@ fn void Blake3Output.root_bytes(&self, usz seek, char[] into) // // macro uint @popcnt(#x) @local => (uint)#x.popcount(); -macro uint @highest_one(#x) @local => 63 ^ (uint)#x.clz(); -macro usz @round_down_to_power_of_2(#x) @local => (usz)1 << @highest_one(#x | 1); +macro uint @highest_one(#x) @local => 63u ^ (uint)#x.clz(); +macro sz @round_down_to_power_of_2(#x) @local => (sz)1 << @highest_one(#x | 1u); -macro left_subtree_len(usz input_len) @local +macro left_subtree_len(sz input_len) @local => @round_down_to_power_of_2((input_len - 1) / CHUNK_SIZE) * CHUNK_SIZE; @@ -582,7 +584,7 @@ macro @g(#state, a, b, c, d, x, y) @local #state[b] = (#state[b] ^ #state[c]).rotr(7); } -macro @round(uint[] state, uint* msg, usz round) @local +macro @round(uint[] state, uint* msg, sz round) @local { char* schedule = &MESSAGE_SCHEDULE[round]; @g(state, 0, 4, 8, 12, msg[schedule[0] ], msg[schedule[1] ]); @@ -595,44 +597,44 @@ macro @round(uint[] state, uint* msg, usz round) @local @g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]); } -fn void compress_pre(uint[] state, uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags) @local @noinline +fn void compress_pre(uint[] state, uint[] cv, char[BLOCK_SIZE] block, sz block_len, long counter, char flags) @local @noinline { uint[16] block_words @noinit; foreach (i, &b : block_words) *b = mem::load((uint*)&block[i * 4], 1); state[0:8] = cv[0:8]; state[8:4] = IV[0:4]; state[12] = (uint)counter; - state[13] = (uint)(counter >> 32); + state[13] = (uint)((ulong)counter >> 32); state[14] = (uint)block_len; state[15] = (uint)flags; for (int i = 0; i < 7; i++) { - @round(state, &block_words[0], (usz)i); + @round(state, &block_words[0], (sz)i); } } -macro compress_in_place(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags) @local +macro compress_in_place(uint[] cv, char[BLOCK_SIZE] block, sz block_len, long counter, char flags) @local { uint[16] state @noinit; compress_pre(state[..], cv, block, block_len, counter, flags); - for (usz i = 0; i < 8; i++) cv[i] = state[i] ^ state[i + 8]; + for (sz i = 0; i < 8; i++) cv[i] = state[i] ^ state[i + 8]; } -macro compress_xof(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags, char[] out) @local +macro compress_xof(uint[] cv, char[BLOCK_SIZE] block, sz block_len, long counter, char flags, char[] out) @local { uint[16] state @noinit; compress_pre(state[..], cv, block, block_len, counter, flags); - $for usz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * $i], state[$i] ^ state[$i + 8], 1); $endfor - $for usz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * (8 + $i)], state[$i + 8] ^ cv[$i], 1); $endfor + $for sz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * $i], state[$i] ^ state[$i + 8], 1); $endfor + $for sz $i = 0; $i < 8; $i++: mem::store((uint*)&out[4 * (8 + $i)], state[$i + 8] ^ cv[$i], 1); $endfor } -macro @xof_many(uint[] cv, char[BLOCK_SIZE] block, usz block_len, ulong counter, char flags, char[] out, usz out_blocks) @local +macro @xof_many(uint[] cv, char[BLOCK_SIZE] block, sz block_len, long counter, char flags, char[] out, sz out_blocks) @local { - for (usz i = 0; i < out_blocks; i++, out = out[BLOCK_SIZE..]) compress_xof(cv, block, block_len, counter + i, flags, out); + for (sz i = 0; i < out_blocks; i++, out = out[BLOCK_SIZE..]) compress_xof(cv, block, block_len, counter + i, flags, out); } -macro hash_one(char* input, usz blocks, uint[] key, ulong counter, char flags, char flags_start, char flags_end, char[] out) @local +macro hash_one(char* input, sz blocks, uint[] key, long counter, char flags, char flags_start, char flags_end, char[] out) @local { uint[8] cv; cv[..] = key[..]; @@ -645,7 +647,7 @@ macro hash_one(char* input, usz blocks, uint[] key, ulong counter, char flags, c foreach (i, c : cv) mem::store((uint*)&out[i * 4], c, 1); } -macro hash_many(char*[] inputs, usz num_inputs, usz blocks, uint[] key, ulong counter, bool $increment_counter, char flags, char flags_start, char flags_end, char* out) @local +macro hash_many(char*[] inputs, sz num_inputs, sz blocks, uint[] key, long counter, bool $increment_counter, char flags, char flags_start, char flags_end, char* out) @local { for (; num_inputs > 0; num_inputs--, inputs = inputs[1..], out += OUT_SIZE) { @@ -655,11 +657,11 @@ macro hash_many(char*[] inputs, usz num_inputs, usz blocks, uint[] key, ulong co } -fn void compress_subtree_to_parent_node(char[] input, uint[] key, ulong chunk_counter, char flags, char[] out, bool use_tbb) @local @noinline +fn void compress_subtree_to_parent_node(char[] input, uint[] key, long chunk_counter, char flags, char[] out, bool use_tbb) @local @noinline { char[MAX_SIMD_DEGREE_OR_2 * OUT_SIZE] cv_array; - usz num_cvs = compress_subtree_wide(input, key, chunk_counter, flags, cv_array[..], use_tbb); + sz num_cvs = compress_subtree_wide(input, key, chunk_counter, flags, cv_array[..], use_tbb); assert(num_cvs <= 2); $if MAX_SIMD_DEGREE_OR_2 > 2: @@ -670,22 +672,22 @@ fn void compress_subtree_to_parent_node(char[] input, uint[] key, ulong chunk_co out[..] = cv_array[:2 * OUT_SIZE]; } -fn usz compress_subtree_wide(char[] input, uint[] key, ulong chunk_counter, char flags, char* out, bool use_tbb) @local @noinline +fn sz compress_subtree_wide(char[] input, uint[] key, long chunk_counter, char flags, char* out, bool use_tbb) @local @noinline { - if (input.len <= @simd_degree() * CHUNK_SIZE) return compress_chunks_parallel(input, key, chunk_counter, flags, out); + if (input.len <= (sz)@simd_degree() * CHUNK_SIZE) return compress_chunks_parallel(input, key, chunk_counter, flags, out); - usz left_input_len = left_subtree_len(input.len); - usz right_input_len = input.len - left_input_len; + sz left_input_len = left_subtree_len(input.len); + sz right_input_len = input.len - left_input_len; char* right_input = &input[left_input_len]; - ulong right_chunk_counter = chunk_counter + (ulong)(left_input_len / CHUNK_SIZE); + long right_chunk_counter = chunk_counter + left_input_len / CHUNK_SIZE; char[2 * MAX_SIMD_DEGREE_OR_2 * OUT_SIZE] cv_array; - usz degree = @simd_degree(); + sz degree = @simd_degree(); if (left_input_len > CHUNK_SIZE && degree == 1) degree = 2; char* right_cvs = &cv_array[degree * OUT_SIZE]; - usz left_n = compress_subtree_wide(input[:left_input_len], key, chunk_counter, flags, &cv_array, use_tbb); - usz right_n = compress_subtree_wide(right_input[:right_input_len], key, right_chunk_counter, flags, right_cvs, use_tbb); + sz left_n = compress_subtree_wide(input[:left_input_len], key, chunk_counter, flags, &cv_array, use_tbb); + sz right_n = compress_subtree_wide(right_input[:right_input_len], key, right_chunk_counter, flags, right_cvs, use_tbb); if (left_n == 1) { @@ -696,10 +698,10 @@ fn usz compress_subtree_wide(char[] input, uint[] key, ulong chunk_counter, char return compress_parents_parallel(cv_array[..], left_n + right_n, key, flags, out); } -fn usz compress_parents_parallel(char[] child_chaining_values, usz num_chaining_values, uint[] key, char flags, char* out) @local @noinline +fn sz compress_parents_parallel(char[] child_chaining_values, sz num_chaining_values, uint[] key, char flags, char* out) @local @noinline { char*[MAX_SIMD_DEGREE_OR_2] parents_array; - usz parents_array_len = 0; + sz parents_array_len = 0; while (num_chaining_values - (2 * parents_array_len) >= 2) { @@ -717,11 +719,11 @@ fn usz compress_parents_parallel(char[] child_chaining_values, usz num_chaining_ return parents_array_len; } -fn usz compress_chunks_parallel(char[] input, uint[] key, ulong chunk_counter, char flags, char* out) @local @noinline +fn sz compress_chunks_parallel(char[] input, uint[] key, long chunk_counter, char flags, char* out) @local @noinline { char*[MAX_SIMD_DEGREE] chunks_array; - usz input_position = 0; - usz chunks_array_len = 0; + sz input_position = 0; + sz chunks_array_len = 0; for (; input.len - input_position >= CHUNK_SIZE; input_position += CHUNK_SIZE) { @@ -732,7 +734,7 @@ fn usz compress_chunks_parallel(char[] input, uint[] key, ulong chunk_counter, c if (input.len <= input_position) return chunks_array_len; - ulong counter = chunk_counter + (ulong)chunks_array_len; + long counter = chunk_counter + chunks_array_len; Blake3ChunkState chunk_state; chunk_state.init(key, flags); chunk_state.chunk_counter = counter; diff --git a/test/stdlib/src/hash/crc32.c3 b/test/stdlib/src/hash/crc32.c3 index a29c3d4..b0e7015 100644 --- a/test/stdlib/src/hash/crc32.c3 +++ b/test/stdlib/src/hash/crc32.c3 @@ -13,7 +13,7 @@ fn void Crc32.init(&self, uint seed = 0) self.result = ~seed; } -fn void Crc32.updatec(&self, char c) +fn void Crc32.update_byte(&self, char c) { self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF]; } diff --git a/test/stdlib/src/hash/crc64.c3 b/test/stdlib/src/hash/crc64.c3 index 4db892f..bf902d6 100644 --- a/test/stdlib/src/hash/crc64.c3 +++ b/test/stdlib/src/hash/crc64.c3 @@ -13,7 +13,7 @@ fn void Crc64.init(&self, uint seed = 0) self.result = seed; } -fn void Crc64.updatec(&self, char c) +fn void Crc64.update_byte(&self, char c) { self.result = (self.result << 8) ^ CRC64_TABLE[(char)((self.result >> 56) ^ c)]; } diff --git a/test/stdlib/src/hash/fnv32a.c3 b/test/stdlib/src/hash/fnv32a.c3 index ad185d8..bdf00b6 100644 --- a/test/stdlib/src/hash/fnv32a.c3 +++ b/test/stdlib/src/hash/fnv32a.c3 @@ -25,7 +25,7 @@ fn void Fnv32a.update(&self, char[] data) *self = h; } -macro void Fnv32a.update_char(&self, char c) +macro void Fnv32a.update_byte(&self, char c) { update(self, c); } diff --git a/test/stdlib/src/hash/fnv64a.c3 b/test/stdlib/src/hash/fnv64a.c3 index 6500f90..03d589c 100644 --- a/test/stdlib/src/hash/fnv64a.c3 +++ b/test/stdlib/src/hash/fnv64a.c3 @@ -25,7 +25,7 @@ fn void Fnv64a.update(&self, char[] data) *self = h; } -macro void Fnv64a.update_char(&self, char c) +macro void Fnv64a.update_byte(&self, char c) { *self = update(*self, c); } diff --git a/test/stdlib/src/hash/gost/streebog.c3 b/test/stdlib/src/hash/gost/streebog.c3 index 367de40..79ac082 100644 --- a/test/stdlib/src/hash/gost/streebog.c3 +++ b/test/stdlib/src/hash/gost/streebog.c3 @@ -20,8 +20,8 @@ struct Streebog ulong[8] n; ulong[8] s; ulong[8] message; - usz index; - usz hash_size; + sz index; + sz hash_size; } @@ -51,7 +51,7 @@ macro @xor_512(#x, #y, #z) @local macro @add_512(#sum, #x) @local { ulong carry = 0; - $for usz $i = 0; $i < 8; $i++: + $for sz $i = 0; $i < 8; $i++: #sum[$i] += #x[$i] + carry; carry = #sum[$i] < #x[$i] ? 1 : (#sum[$i] == #x[$i] ? carry : 0); $endfor @@ -62,10 +62,10 @@ macro @lpsx(#a, #b, #result) @local ulong[8] z @noinit; @xor_512(#a, #b, z); // Do not unroll these loops - produces far too much code at compile-time. - for (usz i = 0; i < 8; i++) + for (sz i = 0; i < 8; i++) { #result[i] = TR[0][(z[0] >> (i << 3)) & 0xff]; - for (usz j = 1; j < 8; j++) #result[i] ^= TR[j][(z[j] >> (i << 3)) & 0xff]; + for (sz j = 1; j < 8; j++) #result[i] ^= TR[j][(z[j] >> (i << 3)) & 0xff]; } } @@ -77,7 +77,7 @@ macro @g_n(#n, #h, #m) @local @lpsx(#h, #n, k_i); @lpsx(k_i, #m, state); // Do not unroll this loop - produces far too much code at compile-time. - for (usz i = 0; i < 11; i++) + for (sz i = 0; i < 11; i++) { @lpsx(k_i, ITERATION_CONSTANTS[i], k_i); @lpsx(k_i, state, state); @@ -109,9 +109,9 @@ fn void Streebog.update(&self, char[] data) { if (self.index) { - usz rest = BLOCK_SIZE - self.index; - usz size = data.len; - usz len = size < rest ? size : rest; + sz rest = BLOCK_SIZE - self.index; + sz size = data.len; + sz len = size < rest ? size : rest; @as_char_view(self.message)[self.index:len] = data[:len]; self.index += size; @@ -121,7 +121,7 @@ fn void Streebog.update(&self, char[] data) data = data[rest..]; self.index = 0; } - bool aligned = 0 == (usz)data.ptr % ulong.sizeof; + bool aligned = 0 == (sz)data.ptr % ulong::size; for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) { if (aligned) @@ -156,11 +156,11 @@ macro char[*] Streebog.final(&self, StreebogLength $hash_size) fn void streebog_final_private(Streebog* self) @local { ulong[8] unprocessed_bits_count; - usz index = self.index >> 3; - usz shift = (self.index & 0b111) * 8; + sz index = self.index >> 3; + sz shift = (self.index & 0b111) * 8; - unprocessed_bits_count[0] = self.index * 8ul; - self.message[index] &= ~(ulong.max << shift); + unprocessed_bits_count[0] = (ulong)(self.index * 8); + self.message[index] &= ~(ulong::max << shift); self.message[index++] ^= 1ul << shift; if (index < 8) self.message[index..] = {}; diff --git a/test/stdlib/src/hash/gost/streebog_internal.c3 b/test/stdlib/src/hash/gost/streebog_internal.c3 index 5d44086..75b8eb1 100644 --- a/test/stdlib/src/hash/gost/streebog_internal.c3 +++ b/test/stdlib/src/hash/gost/streebog_internal.c3 @@ -8,13 +8,13 @@ module std::hash::streebog @private; -const usz BLOCK_SIZE = 64; +const sz BLOCK_SIZE = 64; -const ulong[8] ZERO_512 @align(ulong.sizeof) = {}; +const ulong[8] ZERO_512 @align(ulong::size) = {}; -const ulong[8] STAGE2_512 @align(ulong.sizeof) = { [0] = 512ul, [1..7] = 0 }; +const ulong[8] STAGE2_512 @align(ulong::size) = { [0] = 512ul, [1..7] = 0 }; -const ulong[8][12] ITERATION_CONSTANTS @align(ulong.sizeof) = { +const ulong[8][12] ITERATION_CONSTANTS @align(ulong::size) = { { 0xdd806559f2a64507, 0x05767436cc744d23, 0xa2422a08a460d315, 0x4b7ce09192676901, 0x714eb88d7585c4fc, 0x2f6a76432e45d016, 0xebcb2f81c0657c1f, 0xb1085bda1ecadae9 }, { 0xe679047021b19bb7, 0x55dda21bd7cbcd56, 0x5cb561c2db0aa7ca, 0x9ab5176b12d69958, 0x61d55e0f16b50131, 0xf3feea720a232b98, 0x4fe39d460f70b5d7, 0x6fa3b58aa99d2f1a }, { 0x991e96f50aba0ab2, 0xc2b6f443867adb31, 0xc1c93a376062db09, 0xd3e20fe490359eb1, 0xf2ea7514b1297b7b, 0x06f15e5f529c1f8b, 0x0a39fc286a3d8435, 0xf574dcac2bce2fc7 }, @@ -29,7 +29,7 @@ const ulong[8][12] ITERATION_CONSTANTS @align(ulong.sizeof) = { { 0x48bc924af11bd720, 0xfaf417d5d9b21b99, 0xe71da4aa88e12852, 0x5d80ef9d1891cc86, 0xf82012d430219f9b, 0xcda43c32bcdf1d77, 0xd21380b00449b17a, 0x378ee767f11631ba } }; -const ulong[256][8] TR @align(ulong.sizeof) = { +const ulong[256][8] TR @align(ulong::size) = { { 0xd01f715b5c7ef8e6, 0x16fa240980778325, 0xa8a42e857ee049c8, 0x6ac1068fa186465b, 0x6e417bd7a2e9320b, 0x665c8167a437daab, 0x7666681aa89617f6, 0x4b959163700bdcf5, 0xf14be6b78df36248, 0xc585bd689a625cff, 0x9557d7fca67d82cb, 0x89f0b969af6dd366, 0xb0833d48749f6c35, 0xa1998c23b1ecbc7c, 0x8d70c431ac02a736, 0xd6dfbc2fd0a8b69e, diff --git a/test/stdlib/src/hash/hmac.c3 b/test/stdlib/src/hash/hmac.c3 index 3875da7..da8fa19 100644 --- a/test/stdlib/src/hash/hmac.c3 +++ b/test/stdlib/src/hash/hmac.c3 @@ -16,18 +16,18 @@ fn char[HASH_BYTES] hash(char[] key, char[] message) <* @require output.len > 0 : "Output must be greater than zero" - @require output.len < int.max / HASH_BYTES : "Output is too large" + @require output.len < int::max / HASH_BYTES : "Output is too large" *> fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output) { - usz l = output.len / HASH_BYTES; - usz r = output.len % HASH_BYTES; + sz l = output.len / HASH_BYTES; + sz r = output.len % HASH_BYTES; Hmac hmac; hmac.init(pw); char[] dst_curr = output; - for (usz i = 1; i <= l; i++) + for (sz i = 1; i <= l; i++) { @derive(&hmac, salt, iterations, i, dst_curr[:HASH_BYTES]); dst_curr = dst_curr[HASH_BYTES..]; @@ -83,7 +83,7 @@ fn char[HASH_BYTES] Hmac.final(&self) const IPAD @private = 0x36; const OPAD @private = 0x5C; -macro void @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out) +macro void @derive(Hmac *hmac_start, char[] salt, uint iterations, sz index, char[] out) { assert(out.len == HASH_BYTES); char[HASH_BYTES] tmp @noinit; diff --git a/test/stdlib/src/hash/komi.c3 b/test/stdlib/src/hash/komi.c3 index 12d6013..2eded7f 100644 --- a/test/stdlib/src/hash/komi.c3 +++ b/test/stdlib/src/hash/komi.c3 @@ -62,13 +62,13 @@ fn ulong hash(char[] data, ulong seed = 0) r2h ^= (data.len < 12) ? ((data[data.len - 3] | ((ulong)data[data.len - 2] << 8) | ((ulong)data[data.len - 1] << 16) | ((ulong)1 << 24)) >> ((data.len * 8) ^ 88)) - : (((mem::load((uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (128 - data.len * 8)) << 32 | mem::load((uint*)&data[8], 1)); + : (((mem::load((uint*)&data[^4], 1) | (1UL << 32)) >> (128 - data.len * 8)) << 32 | mem::load((uint*)&data[8], 1)); } else if (data.len != 0) { r1h ^= (data.len < 4) - ? (((ulong)1 << (data.len * 8)) ^ data[0] ^ (data.len > 1 ? (ulong)data[1] << 8 : 0) ^ (data.len > 2 ? (ulong)data[2] << 16 : 0)) - : (((mem::load((uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (64 - data.len * 8)) << 32 | mem::load((uint*)&data[0], 1)); + ? (((ulong)1 << (data.len * 8u)) ^ data[0] ^ (data.len > 1 ? (ulong)data[1] << 8 : 0u) ^ (data.len > 2 ? (ulong)data[2] << 16u : 0u)) + : (((mem::load((uint*)&data[^4], 1) | (1UL << 32)) >> (64 - data.len * 8u)) << 32 | mem::load((uint*)&data[0], 1)); } } else if (data.len < 32) diff --git a/test/stdlib/src/hash/md5.c3 b/test/stdlib/src/hash/md5.c3 index d3a9e29..1d7d4b8 100644 --- a/test/stdlib/src/hash/md5.c3 +++ b/test/stdlib/src/hash/md5.c3 @@ -40,14 +40,14 @@ fn void Md5.init(&self) fn void Md5.update(&ctx, char[] data) { uint saved_lo = ctx.lo; - if ((ctx.lo = (saved_lo + data.len) & 0x1fffffff) < saved_lo) ctx.hi++; - ctx.hi += data.len >> 29; + if ((ctx.lo = (uint)(saved_lo + (usz)data.len) & 0x1fffffff) < saved_lo) ctx.hi++; + ctx.hi += (uint)(data.len >> 29); - usz used = (usz)saved_lo & 0x3f; + sz used = (sz)saved_lo & 0x3f; if (used) { - usz available = 64 - used; + sz available = 64 - used; if (data.len < available) { @@ -61,17 +61,17 @@ fn void Md5.update(&ctx, char[] data) if (data.len >= 64) { - data = body(ctx, data, data.len & ~(usz)0x3f)[:data.len & 0x3f]; + data = body(ctx, data, (sz)data.len & ~(sz)0x3f)[:data.len & 0x3f]; } ctx.buffer[:data.len] = data[..]; } fn char[HASH_BYTES] Md5.final(&ctx) { - usz used = (usz)ctx.lo & 0x3f; + sz used = (sz)ctx.lo & 0x3f; ctx.buffer[used++] = 0x80; - usz available = 64 - used; + sz available = 64 - used; if (available < 8) { @@ -97,9 +97,7 @@ fn char[HASH_BYTES] Md5.final(&ctx) return res; } -module std::hash::md5 @private; - -const uint[64] MD5_T @private = { +const uint[64] MD5_T @local = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, @@ -118,7 +116,7 @@ const uint[64] MD5_T @private = { 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; -const int[16] MD5_S @private = { +const int[16] MD5_S @local = { 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, @@ -126,13 +124,13 @@ const int[16] MD5_S @private = { }; // Implementation -macro @f(x, y, z) => z ^ (x & (y ^ z)); -macro @g(x, y, z) => y ^ (z & (x ^ y)); -macro @h(x, y, z) => (x ^ y) ^ z; -macro @h2(x, y, z) => x ^ (y ^ z); -macro @i(x, y, z) => y ^ (x | ~z); +macro @f(x, y, z) @local => z ^ (x & (y ^ z)); +macro @g(x, y, z) @local => y ^ (z & (x ^ y)); +macro @h(x, y, z) @local => (x ^ y) ^ z; +macro @h2(x, y, z) @local => x ^ (y ^ z); +macro @i(x, y, z) @local => y ^ (x | ~z); -macro void @step(#f, a, b, c, d, ptr, n, t, s) +macro void @step(#f, a, b, c, d, ptr, n, t, s) @local { *a += #f(b, c, d) + mem::load((uint *)&ptr[n * 4], 2) + t; *a = (*a << s) | ((*a & 0xffffffff) >> (32 - s)); @@ -140,7 +138,7 @@ macro void @step(#f, a, b, c, d, ptr, n, t, s) } -fn char* body(Md5* ctx, void* data, usz size) +fn char* body(Md5* ctx, void* data, sz size) @local { char* ptr = data; uint a = ctx.a; diff --git a/test/stdlib/src/hash/metro128.c3 b/test/stdlib/src/hash/metro128.c3 index ea645db..b1e26d0 100644 --- a/test/stdlib/src/hash/metro128.c3 +++ b/test/stdlib/src/hash/metro128.c3 @@ -30,7 +30,7 @@ struct MetroHash128 ulong[4] stomach_64; char[32] stomach; } - ulong bytes; + long bytes; } @@ -56,11 +56,12 @@ fn void MetroHash128.init(&self, ulong seed = 0) fn void MetroHash128.update(&self, char[] data) { + sz remainder = (sz)(self.bytes % 32); if (self.bytes % 32) // partial buffer { - ulong to_fill = min(data.len, (32 - (self.bytes % 32))); + sz to_fill = min(data.len, 32 - remainder); - self.stomach[(self.bytes % 32):to_fill] = data[:to_fill]; + self.stomach[remainder:to_fill] = data[:to_fill]; data = data[to_fill..]; self.bytes += to_fill; @@ -77,10 +78,10 @@ fn void MetroHash128.update(&self, char[] data) for (; data.len >= 32; data = data[32:^32]) { - self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2]; - self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3]; - self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0]; - self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1]; + self.state[0] += mem::load(&((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2]; + self.state[1] += mem::load(&((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3]; + self.state[2] += mem::load(&((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0]; + self.state[3] += mem::load(&((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1]; } // Gobble up the leftover bytes. Nom nom. @@ -112,7 +113,7 @@ fn uint128 MetroHash128.final(&self) if (final_data.len >= 8) { - self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3]; + self.state[0] += mem::load((ulong*)final_data.ptr, 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3]; self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(27) * K[1]; final_data = final_data[8:^8]; @@ -120,7 +121,7 @@ fn uint128 MetroHash128.final(&self) if (final_data.len >= 4) { - self.state[1] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(33) * K[3]; + self.state[1] += mem::load((uint*)final_data.ptr, 1) * K[2]; self.state[1] = self.state[1].rotr(33) * K[3]; self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(46) * K[0]; final_data = final_data[4:^4]; @@ -128,7 +129,7 @@ fn uint128 MetroHash128.final(&self) if (final_data.len >= 2) { - self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3]; + self.state[0] += mem::load((ushort*)final_data.ptr, 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3]; self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(22) * K[1]; final_data = final_data[2:^2]; diff --git a/test/stdlib/src/hash/metro64.c3 b/test/stdlib/src/hash/metro64.c3 index f75f174..b33bd9e 100644 --- a/test/stdlib/src/hash/metro64.c3 +++ b/test/stdlib/src/hash/metro64.c3 @@ -30,7 +30,7 @@ struct MetroHash64 ulong[4] stomach_64; char[32] stomach; } - ulong bytes; + long bytes; ulong vseed; } @@ -59,7 +59,7 @@ fn void MetroHash64.update(&self, char[] data) { if (self.bytes % 32) // partial buffer { - ulong to_fill = min(data.len, (32 - (self.bytes % 32))); + long to_fill = min(data.len, (32 - (self.bytes % 32))); self.stomach[(self.bytes % 32):to_fill] = data[:to_fill]; @@ -78,10 +78,10 @@ fn void MetroHash64.update(&self, char[] data) for (; data.len >= 32; data = data[32:^32]) { - self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2]; - self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3]; - self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0]; - self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1]; + self.state[0] += mem::load(&((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2]; + self.state[1] += mem::load(&((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3]; + self.state[2] += mem::load(&((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0]; + self.state[3] += mem::load(&((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1]; } // Gobble up the leftover bytes. Nom nom. @@ -105,8 +105,8 @@ fn ulong MetroHash64.final(&self) if (final_data.len >= 16) { - self.state[1] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(29) * K[3]; - self.state[2] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[1], 1) * K[2]; self.state[2] = self.state[2].rotr(29) * K[3]; + self.state[1] = self.state[0] + mem::load(&((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(29) * K[3]; + self.state[2] = self.state[0] + mem::load(&((ulong*)final_data.ptr)[1], 1) * K[2]; self.state[2] = self.state[2].rotr(29) * K[3]; self.state[1] ^= (self.state[1] * K[0]).rotr(21) + self.state[2]; self.state[2] ^= (self.state[2] * K[3]).rotr(21) + self.state[1]; self.state[0] += self.state[2]; @@ -116,7 +116,7 @@ fn ulong MetroHash64.final(&self) if (final_data.len >= 8) { - self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[3]; + self.state[0] += mem::load((ulong*)final_data.ptr, 1) * K[3]; self.state[0] ^= self.state[0].rotr(55) * K[1]; final_data = final_data[8:^8]; @@ -124,7 +124,7 @@ fn ulong MetroHash64.final(&self) if (final_data.len >= 4) { - self.state[0] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[3]; + self.state[0] += mem::load((uint*)final_data.ptr, 1) * K[3]; self.state[0] ^= self.state[0].rotr(26) * K[1]; final_data = final_data[4:^4]; @@ -132,7 +132,7 @@ fn ulong MetroHash64.final(&self) if (final_data.len >= 2) { - self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[3]; + self.state[0] += mem::load((ushort*)final_data.ptr, 1) * K[3]; self.state[0] ^= self.state[0].rotr(48) * K[1]; final_data = final_data[2:^2]; diff --git a/test/stdlib/src/hash/murmur.c3 b/test/stdlib/src/hash/murmur.c3 index 5c6dfc0..ae231b0 100644 --- a/test/stdlib/src/hash/murmur.c3 +++ b/test/stdlib/src/hash/murmur.c3 @@ -3,7 +3,7 @@ module std::hash::murmur3; <* @param [in] data : "The data to hash" @param seed : "The seed to use for hashing" - @require (data.len / 4) <= int.max : "Too much data" + @require (data.len / 4) <= int::max : "Too much data" *> fn uint hash32(char[] data, uint seed) { @@ -49,12 +49,12 @@ fn uint hash32(char[] data, uint seed) <* @param [in] data : "The data to hash" @param seed : "The seed to use for hashing" - @require (data.len / 16) <= int.max : "Too much data" + @require (data.len / 16) <= int::max : "Too much data" *> fn uint128 hash128_64(char[] data, uint seed) { - ulong len = data.len; + sz len = data.len; int nblocks = (int)(len / 16); ulong h1 = seed; @@ -105,8 +105,8 @@ fn uint128 hash128_64(char[] data, uint seed) } - h1 ^= len; - h2 ^= len; + h1 ^= (uint)len; + h2 ^= (uint)len; h1 += h2; h2 += h1; @@ -124,12 +124,12 @@ fn uint128 hash128_64(char[] data, uint seed) <* @param [in] data : "The data to hash" @param seed : "The seed to use for hashing" - @require data.len <= uint.max : "Too much data" + @require data.len <= int::max : "Too much data" *> fn uint128 hash128_32(char[] data, uint seed) { - uint len = data.len; - int nblocks = (int)(len / 16); + int len = data.len; + int nblocks = len / 16; uint h1 = seed; uint h2 = seed; @@ -190,7 +190,7 @@ fn uint128 hash128_32(char[] data, uint seed) k1 *= C1; k1 = k1.rotl(15); k1 *= C2; h1 ^= k1; } - h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + h1 ^= (uint)len; h2 ^= (uint)len; h3 ^= (uint)len; h4 ^= (uint)len; h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; diff --git a/test/stdlib/src/hash/poly1305.c3 b/test/stdlib/src/hash/poly1305.c3 index 5fe751a..e89547e 100644 --- a/test/stdlib/src/hash/poly1305.c3 +++ b/test/stdlib/src/hash/poly1305.c3 @@ -22,7 +22,7 @@ struct Poly1305 uint128 r; // secret portion of key uint128 nonce; // initialization vector, derived from key char[TAG_SIZE] temp; // last partial ingestion state - usz num; // index into last partial ingestion + sz num; // index into last partial ingestion // Additional, cached state information: ulong r0; ulong r1; @@ -66,8 +66,8 @@ fn void Poly1305.init(&self, char[KEY_SIZE] key) .nonce = mem::load((uint128*)&key[16], 1) }; - self.r0 = @unaligned_load(((ulong*)&self.r)[0], 1); - self.r1 = @unaligned_load(((ulong*)&self.r)[1], 1); + self.r0 = mem::load(&((ulong*)&self.r)[0], 1); + self.r1 = mem::load(&((ulong*)&self.r)[1], 1); self.s1 = self.r1 + (self.r1 >> 2); } @@ -77,7 +77,7 @@ fn void Poly1305.update(&self, char[] input) { if (self.num) // currently between consuming full blocks? { - usz rem = BLOCK_SIZE - self.num; + sz rem = BLOCK_SIZE - self.num; if (input.len < rem) { self.temp[self.num:input.len] = input[..]; // saving another partial block @@ -90,7 +90,7 @@ fn void Poly1305.update(&self, char[] input) input = input[rem..]; } - usz even_length = input.len - (input.len % BLOCK_SIZE); + sz even_length = input.len - (input.len % BLOCK_SIZE); if (even_length >= BLOCK_SIZE) { _blocks(self, input[:even_length]); // consume blocks @@ -112,13 +112,13 @@ fn char[TAG_SIZE] Poly1305.final(&self) _blocks(self, self.temp[..], 0); // chomp } - uint128 t = (uint128)self.h[0] + 5; + uint128 t = (uint128)self.h[0] + 5u; ulong g0 = (ulong)t; t = (uint128)self.h[1] + (t >> 64); ulong g1 = (ulong)t; - ulong mask = 0 - ((self.h[2] + (ulong)(t >> 64)) >> 2); + ulong mask = 0u - ((self.h[2] + (ulong)(t >> 64)) >> 2); self.h[0] = (self.h[0] & ~mask) | (g0 & mask); self.h[1] = (self.h[1] & ~mask) | (g1 & mask); diff --git a/test/stdlib/src/hash/ripemd.c3 b/test/stdlib/src/hash/ripemd.c3 index 960f81a..00cd41e 100644 --- a/test/stdlib/src/hash/ripemd.c3 +++ b/test/stdlib/src/hash/ripemd.c3 @@ -3,7 +3,7 @@ // a copy of which can be found in the LICENSE_STDLIB file. module std::hash::ripemd; -const usz[4] PERMISSIBLE_SIZES_BITS = { 128, 160, 256, 320 }; +const sz[4] PERMISSIBLE_SIZES_BITS = { 128, 160, 256, 320 }; <* Unchanging block size. *> const BLOCK_SIZE = 64; @@ -34,7 +34,7 @@ struct RipeMd { uint[10] state; uint[16] buffer; - ulong byte_count; + long byte_count; } @@ -70,8 +70,8 @@ fn void RipeMd.init(&self) *> fn void RipeMd.update(&self, char[] data) { - usz $bufsz = $sizeof(self.buffer); - uint avail = $bufsz - (uint)(self.byte_count & 0x3f); + sz $bufsz = @sizeof(self.buffer); + sz avail = $bufsz - (self.byte_count & 0x3f); self.byte_count += data.len; if (avail > data.len) @@ -84,11 +84,11 @@ fn void RipeMd.update(&self, char[] data) self.transform(self.buffer); data = data[avail..]; - for (bool is_aligned = 0 == (usz)data.ptr % uint.sizeof; data.len >= $bufsz; data = data[$bufsz..]) + for (bool is_aligned = 0 == (sz)data.ptr % uint::size; data.len >= $bufsz; data = data[$bufsz..]) { if (is_aligned) // when aligned, this optimization is a ~10-20% performance boost { - self.transform(((uint*)data.ptr)[:$bufsz / uint.sizeof]); + self.transform(((uint*)data.ptr)[:$bufsz / uint::size]); } else { @@ -106,13 +106,13 @@ fn void RipeMd.update(&self, char[] data) *> fn char[DIGEST_BYTES] RipeMd.final(&self) { - char[DIGEST_BYTES] result @align(uint.sizeof); + char[DIGEST_BYTES] result @align(uint::size); static char[64] padding = { [0] = 0x80, [1..63] = 0x00 }; // Napkin maffs. - ulong bits = (ulong)self.byte_count << 3; - uint index = (uint)self.byte_count & 0x3f; - uint padlen = (index < 56) ? (56 - index) : ((64 + 56) - index); + long bits = self.byte_count << 3; + int index = (int)self.byte_count & 0x3f; + int padlen = (index < 56) ? (56 - index) : ((64 + 56) - index); // Update with padding, then append bit-length of digested message. self.update(padding[:padlen]); @@ -122,7 +122,7 @@ fn char[DIGEST_BYTES] RipeMd.final(&self) defer mem::zero_volatile(@as_char_view(*self)); // Copy and return. - ((uint*)&result)[:DIGEST_BYTES / uint.sizeof] = self.state[:DIGEST_BYTES / uint.sizeof]; + ((uint*)&result)[:DIGEST_BYTES / uint::size] = self.state[:DIGEST_BYTES / uint::size]; return result; } @@ -136,14 +136,14 @@ fn uint f5(uint x, uint y, uint z) @inline @local => x ^ (y | ~z); // The primary workhorse of the digest function. Behavior only changes slightly. // All 128/256 inputs will expand with 'e' as 0 - they don't use it anyway. -macro @round(#a, b, #c, d, e, #func, k, x, s) @local +macro @round(#a, b, #c, d, uint e, #func, uint k, x, int s) @local { #a += #func(b, #c, d) + x + k; $switch: $case DIGEST_BITS == 128 ||| DIGEST_BITS == 256: - #a = #a.rotl(s); + #a = #a.rotl((uint)s); $case DIGEST_BITS == 160 ||| DIGEST_BITS == 320: - #a = #a.rotl(s) + e; + #a = #a.rotl((uint)s) + e; #c = #c.rotl(10); $default: $error "Invalid digest bits"; @@ -155,12 +155,12 @@ macro @round(#a, b, #c, d, e, #func, k, x, s) @local @param in : "Data or message block to process." *> -fn void RipeMd.transform(&self, uint[BLOCK_SIZE / uint.sizeof] in) @noinline => self.@transform(in); +fn void RipeMd.transform(&self, uint[BLOCK_SIZE / uint::size] in) @noinline => self.@transform(in); <* @param in : "Data or message block to process." *> -macro RipeMd.@transform(&self, uint[BLOCK_SIZE / uint.sizeof] in) @local +macro RipeMd.@transform(&self, uint[BLOCK_SIZE / uint::size] in) @local { uint aa, bb, cc, dd, ee, aaa, bbb, ccc, ddd, eee; uint[] state = self.state[..]; diff --git a/test/stdlib/src/hash/sha1.c3 b/test/stdlib/src/hash/sha1.c3 index 2bc7cab..c3ecd84 100644 --- a/test/stdlib/src/hash/sha1.c3 +++ b/test/stdlib/src/hash/sha1.c3 @@ -46,22 +46,22 @@ fn void Sha1.init(&self) <* @param [in] data - @require data.len <= uint.max + @require data.len <= uint::max *> fn void Sha1.update(&self, char[] data) { uint j = self.count[0]; - uint len = data.len; + uint len = (uint)data.len; if ((self.count[0] += len << 3) < j) self.count[1]++; self.count[1] += len >> 29; - j = (j >> 3) & 63; + j = (j >> 3) & 63u; uint i; - if (j + len > 63) + if (j + len > 63u) { - i = 64 - j; + i = 64u - j; self.buffer[j..] = data[:i]; sha1_transform(&self.state, &self.buffer); - for (; i + 63 < len; i += 64) + for (; i + 63u < len; i += 64u) { sha1_transform(&self.state, &data[i]); } @@ -74,19 +74,19 @@ fn void Sha1.update(&self, char[] data) fn char[HASH_BYTES] Sha1.final(&self) { char[8] finalcount; - for (uint i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF); } self.update((char[]){ 0o200 }); - while ((self.count[0] & 504) != 448) + while ((self.count[0] & 504u) != 448u) { self.update((char[]){ 0 }); } self.update(&finalcount); char[HASH_BYTES] digest; - for (uint i = 0; i < HASH_BYTES; i++) + for (int i = 0; i < HASH_BYTES; i++) { digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF); } @@ -105,8 +105,8 @@ union Long16 @local macro uint blk(Long16* block, i) @local { - return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15] - ^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1)); + return (block.l[i & 15u] = (block.l[(i + 13u) & 15u] ^ block.l[(i + 8u) & 15u] + ^ block.l[(i + 2u) & 15u] ^ block.l[i & 15u]).rotl(1u)); } macro uint blk0(Long16* block, i) @local @@ -114,8 +114,8 @@ macro uint blk0(Long16* block, i) @local $if env::BIG_ENDIAN: return block.l[i]; $else - return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00) - | (block.l[i].rotl(8) & 0x00FF00FF); + return block.l[i] = (block.l[i].rotl(24u) & 0xFF00FF00) + | (block.l[i].rotl(8u) & 0x00FF00FF); $endif } diff --git a/test/stdlib/src/hash/sha256.c3 b/test/stdlib/src/hash/sha256.c3 index 410175d..8f167f4 100644 --- a/test/stdlib/src/hash/sha256.c3 +++ b/test/stdlib/src/hash/sha256.c3 @@ -28,9 +28,9 @@ macro uint @sigma1(uint x) @local => x.rotr(17) ^ x.rotr(19) ^ (x >> 10); struct Sha256 { - uint[8] state @align(usz.sizeof); - char[BLOCK_SIZE] buffer @align(ulong.sizeof); // must align along bitcount sizeof - see `final` - ulong bitcount; + uint[8] state @align(sz::size); + char[BLOCK_SIZE] buffer @align(ulong::size); // must align along bitcount sizeof - see `final` + long bitcount; } <* @@ -55,17 +55,17 @@ fn void Sha256.init(&self) => *self = { <* @param [in] data - @require data.len <= uint.max + @require data.len <= uint::max *> fn void Sha256.update(&self, char[] data) { - uint buffer_pos = (uint)(self.bitcount >> 3) % BLOCK_SIZE; - self.bitcount += (ulong)data.len << 3; // always record ingested bits count immediately + int buffer_pos = (int)((self.bitcount >> 3) % BLOCK_SIZE); + self.bitcount += (long)data.len << 3; // always record ingested bits count immediately // Get the buffer position back to 0 if we're midway through consuming some data. if (buffer_pos > 0 && buffer_pos < BLOCK_SIZE) { - usz len = min(BLOCK_SIZE - buffer_pos, data.len); + sz len = min(BLOCK_SIZE - buffer_pos, data.len); self.buffer[buffer_pos:len] = data[:len]; data = data[len..]; if (buffer_pos + len == BLOCK_SIZE) _transform(self); @@ -73,7 +73,7 @@ fn void Sha256.update(&self, char[] data) // When the data pointer is aligned, we can disregard unaligned loading in the `transform` macro. // We do this here from the outer call to reduce the expense of checking alignment on every single block. - if (0 == (usz)data.ptr % usz.sizeof) + if (0 == (sz)data.ptr % sz::size) { for (; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..]) _transform(self, (uint*)data.ptr); } @@ -92,8 +92,8 @@ fn void Sha256.update(&self, char[] data) fn char[HASH_SIZE] Sha256.final(&self) { - char[HASH_SIZE] hash @align(uint.sizeof); - ulong i = (self.bitcount / 8) % BLOCK_SIZE; + char[HASH_SIZE] hash @align(uint::size); + long i = (self.bitcount / 8) % BLOCK_SIZE; // Append 0x80 to the buffer self.buffer[i++] = 0x80; @@ -109,12 +109,12 @@ fn char[HASH_SIZE] Sha256.final(&self) self.buffer[i..(BLOCK_SIZE - 8)] = 0x00; // Append the bitcount in big-endian format - *(ulong*)(&self.buffer[BLOCK_SIZE - 8]) = env::BIG_ENDIAN ??? self.bitcount : bswap(self.bitcount); + *(long*)(&self.buffer[BLOCK_SIZE - 8]) = env::BIG_ENDIAN ??? self.bitcount : bswap(self.bitcount); _transform(self); // Convert state to the final hash - foreach (x, s : self.state) *(uint*)(&hash[x * uint.sizeof]) = env::BIG_ENDIAN ??? s : bswap(s); + foreach (x, s : self.state) *(uint*)(&hash[x * uint::size]) = env::BIG_ENDIAN ??? s : bswap(s); return hash; } @@ -136,7 +136,7 @@ macro _do_transform(Sha256* self, uint* incoming = null, bool $aligned = true) @ @as_char_view(m)[:BLOCK_SIZE] = @as_char_view(incoming)[:BLOCK_SIZE]; $else // Unrolling this seems to make the hash slower. - for (i = 0; i < 16; ++i) m[i] = bswap($aligned ??? incoming[i] : @unaligned_load(incoming[i], 1)); + for (i = 0; i < 16; ++i) m[i] = bswap($aligned ??? incoming[i] : mem::load(&incoming[i], 1)); $endif for (i = 16; i < 64; i++) m[i] = @sigma1(m[i - 2]) + m[i - 7] + @sigma0(m[i - 15]) + m[i - 16]; @@ -150,7 +150,7 @@ macro _do_transform(Sha256* self, uint* incoming = null, bool $aligned = true) @ g = self.state[6]; h = self.state[7]; - $for usz $i = 0; $i < 64; $i++: + $for sz $i = 0; $i < 64; $i++: t1 = h + @_sigma1(e) + @ch(e, f, g) + K[$i] + m[$i]; t2 = @_sigma0(a) + @maj(a, b, c); h = g; diff --git a/test/stdlib/src/hash/sha512.c3 b/test/stdlib/src/hash/sha512.c3 index 8abc311..0e27339 100644 --- a/test/stdlib/src/hash/sha512.c3 +++ b/test/stdlib/src/hash/sha512.c3 @@ -17,7 +17,7 @@ const HASH_SIZE = 64; struct Sha512 { - ulong length; + long length; ulong[8] hash_state; char[BLOCK_SIZE] buffer; } @@ -132,27 +132,25 @@ fn void Sha512.init(&self) <* @param [in] data - @require data.len <= ulong.max + @require data.len <= long::max *> fn void Sha512.update(&self, char[] data) { char* p = data.ptr; - ulong len = data.len; - ulong l; - ulong r = self.length % 128; + long len = data.len; + long r = self.length % 128; self.length += len; if (r) { - if (len < (128 - r)) + if (len < 128 - r) { - for (l = 0; l < len; ++l) self.buffer[r+l] = p[l]; - + for (long l = 0; l < len; l++) self.buffer[r+l] = p[l]; return; } - for (l = 0; l < 128 - r; ++l) self.buffer[r+l] = p[l]; + for (long l = 0; l < 128 - r; l++) self.buffer[r+l] = p[l]; len -= (128 - r); p = &p[128 - r]; @@ -161,8 +159,7 @@ fn void Sha512.update(&self, char[] data) } for (; len >= 128; len -= 128, p = &p[128]) sha512_transform(&self.hash_state, p); - - for (l = 0; l < len; ++l) self.buffer[l] = p[l]; + for (long l = 0; l < len; ++l) self.buffer[l] = p[l]; } @@ -170,21 +167,20 @@ fn char[HASH_SIZE] Sha512.final(&self) { char[HASH_SIZE] hash; - int i; - ulong r = self.length % 128; + int r = (int)(self.length % 128); self.buffer[r++] = 0x80; if (r > 112) { - for (i = 0; i < 128 - r; ++i) self.buffer[r+i] = 0; + for (int i = 0; i < 128 - r; i++) self.buffer[r+i] = 0; r = 0; sha512_transform(&self.hash_state, &self.buffer); } - for (i = 0; i < 120 - r; ++i) self.buffer[r+i] = 0; + for (int i = 0; i < 120 - r; i++) self.buffer[r + i] = 0; self.length *= 8; @@ -199,7 +195,7 @@ fn char[HASH_SIZE] Sha512.final(&self) sha512_transform(&self.hash_state, &self.buffer); - for (i = 0; i < 8; ++i) + for (int i = 0; i < 8; ++i) { hash[(8 * i)] = (char)(self.hash_state[i] >> 56); hash[(8 * i) + 1] = (char)(self.hash_state[i] >> 48); diff --git a/test/stdlib/src/hash/siphash.c3 b/test/stdlib/src/hash/siphash.c3 index bcd2cc0..94f3638 100644 --- a/test/stdlib/src/hash/siphash.c3 +++ b/test/stdlib/src/hash/siphash.c3 @@ -46,7 +46,7 @@ alias hash = siphash::hash { uint128, 4, 8 }; Read more: https://en.wikipedia.org/wiki/SipHash - @require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong." + @require OutType::typeid == uint128::typeid || OutType::typeid == ulong::typeid : "Module OutType must be either uint128 or ulong." *> module std::hash::siphash ; @@ -56,7 +56,7 @@ struct SipHash ulong[4] v; long m; int m_idx; - usz len; + sz len; } fn OutType hash(char[] data, uint128 key) @@ -78,7 +78,7 @@ fn void SipHash.init(&self, uint128 key) 0x7465_6462_7974_6573 ^ key_64[1], }; - $if OutType.typeid == uint128.typeid : + $if OutType::typeid == uint128::typeid : self.v[1] ^= 0xEE; $endif } @@ -97,13 +97,13 @@ fn void SipHash.update(&self, char[] data) if (self.m_idx < 8) continue; // This runs every time the m_idx is 8, then it resets to 0. - self.v[3] ^= self.m; + self.v[3] ^= (ulong)self.m; $for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop siphash_round(self); $endfor - self.v[0] ^= self.m; + self.v[0] ^= (ulong)self.m; self.m_idx = 0; self.m = 0; @@ -116,7 +116,7 @@ fn OutType SipHash.final(&self) self.update(last[(self.m_idx < 7 ? self.m_idx : 7)..]); - $if OutType.typeid == uint128.typeid : + $if OutType::typeid == uint128::typeid : self.v[2] ^= 0xEE; $else self.v[2] ^= 0xFF; @@ -126,7 +126,7 @@ fn OutType SipHash.final(&self) siphash_round(self); $endfor - $if OutType.typeid == ulong.typeid : + $if OutType::typeid == ulong::typeid : return self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]; $else ulong lo = self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]; diff --git a/test/stdlib/src/hash/whirlpool/whirlpool.c3 b/test/stdlib/src/hash/whirlpool/whirlpool.c3 index e95302a..7414fdd 100644 --- a/test/stdlib/src/hash/whirlpool/whirlpool.c3 +++ b/test/stdlib/src/hash/whirlpool/whirlpool.c3 @@ -10,7 +10,7 @@ import std::hash::hmac; const BLOCK_SIZE = 64; const HASH_SIZE = 64; -const BLOCK_128 = 64 / int128.sizeof; +const BLOCK_128 = 64 / int128::size; struct Whirlpool { @@ -41,15 +41,15 @@ fn char[HASH_SIZE] hash(char[] data) macro void Whirlpool.init(&self) => *self = { }; <* - @require data.len <= isz.max : "Update with smaller slices" + @require data.len <= sz::max : "Update with smaller slices" *> fn void Whirlpool.update(&self, char[] data) { char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it - uint to_pad = BLOCK_SIZE - remainder; // how many bytes must be filled to get the BLOCK_SIZE + int to_pad = BLOCK_SIZE - remainder; // how many bytes must be filled to get the BLOCK_SIZE uint128 prev_ctr = self.counter_low; - self.counter_low += data.len; + self.counter_low += (uint128)data.len; // Handle counter rollover by just adding 1 onto 'high'. The overflow amount in 'low' remains valid. // Since 'data' is limited to INT64_MAX, there are no edge cases where 'high' must be incremented by more than 1. @@ -59,7 +59,7 @@ fn void Whirlpool.update(&self, char[] data) // Pad partial blocks of input data. if (remainder > 0) { - usz span = to_pad < data.len ? to_pad : data.len; + sz span = to_pad < data.len ? to_pad : data.len; self.block[remainder:span] = data[:span]; // When there wasn't enough data ingested to fill a block, just return to allow more incoming data to fill in. @@ -81,7 +81,7 @@ fn void Whirlpool.update(&self, char[] data) // Any leftovers should be swept into the 'block' buffer in the context for the next update or for 'final'. if (data.len) { - usz leftover_span = (BLOCK_SIZE < data.len ? BLOCK_SIZE : data.len); + sz leftover_span = (BLOCK_SIZE < data.len ? BLOCK_SIZE : data.len); self.block[:leftover_span] = data[:leftover_span]; } @@ -109,14 +109,14 @@ fn char[HASH_SIZE] Whirlpool.final(&self) // Insert the big-endian values of the counter as a suffix of the block. // This 'counter' value is actually the number of BITS processed, hence the << 3 (or x8). - self.block_128[2] = $$bswap(self.counter_high << 3 | (self.counter_low >> 125 & 0x07)); - self.block_128[3] = $$bswap(self.counter_low << 3); + self.block_128[2] = (int128)$$bswap(self.counter_high << 3 | (self.counter_low >> 125 & 0x07)); + self.block_128[3] = (int128)$$bswap(self.counter_low << 3); // Process the final block. _process_block(self, &self.block); // Each ulong in the resultant hash should be bit-swapped before the final return. - char[HASH_SIZE] hash @align(ulong.alignof); + char[HASH_SIZE] hash @align(ulong::alignment); ulong* hash_ref = (ulong*)&hash; $for var $i = 0; $i < 8; ++$i : diff --git a/test/stdlib/src/hash/wyhash2.c3 b/test/stdlib/src/hash/wyhash2.c3 index abf3175..361f1bb 100644 --- a/test/stdlib/src/hash/wyhash2.c3 +++ b/test/stdlib/src/hash/wyhash2.c3 @@ -8,7 +8,7 @@ module std::hash::wyhash2; -fn ulong wyr3(char* in, usz len) @inline +fn ulong wyr3(char* in, sz len) @inline => ((ulong)in[0] << 16) | ((ulong)in[len >> 1] << 8) | (ulong)in[len - 1]; @@ -51,6 +51,6 @@ fn ulong hash(char[] input, ulong seed = 0) uint128 r = ((uint128)a ^ 0xe703_7ed1_a0b4_28db) * ((uint128)b ^ seed); ulong pre_res = (ulong)r ^ (ulong)(r >> 64); - r = ((uint128)0xe703_7ed1_a0b4_28db ^ input.len) * (uint128)pre_res; + r = (0xe703_7ed1_a0b4_28db ^ (uint128)input.len) * (uint128)pre_res; return (ulong)r ^ (ulong)(r >> 64); } diff --git a/test/stdlib/src/io/bits.c3 b/test/stdlib/src/io/bits.c3 index dd4c9c8..324e217 100644 --- a/test/stdlib/src/io/bits.c3 +++ b/test/stdlib/src/io/bits.c3 @@ -4,7 +4,7 @@ struct BitReader { InStream reader; uint bits; - uint len; + int len; } fn void BitReader.init(&self, InStream byte_reader) @@ -18,10 +18,10 @@ fn void BitReader.clear(&self) @inline } <* - @require nbits <= 8 - @require self.len + nbits <= uint.sizeof * 8 + @require nbits <= 8 && nbits > 0 + @require self.len + (sz)nbits <= uint::size * 8 *> -fn char? BitReader.read_bits(&self, uint nbits) +fn char? BitReader.read_bits(&self, int nbits) { uint bits = self.bits; if (self.len < nbits) @@ -34,7 +34,7 @@ fn char? BitReader.read_bits(&self, uint nbits) self.len += 8; } self.len -= nbits; - uint mask = (1 << nbits) - 1; + uint mask = (1u << nbits) - 1u; return (char)((bits >> self.len) & mask); } @@ -42,7 +42,7 @@ struct BitWriter { OutStream writer; uint bits; - uint len; + int len; } // c3 doesn't allow to shift more than bit width of a variable, @@ -56,11 +56,11 @@ fn void BitWriter.init(&self, OutStream byte_writer) fn void? BitWriter.flush(&self) { - if (self.len == 0) return; + if (!self.len) return; - int padding = ($sizeof(self.bits) * 8 - self.len); + int padding = (@sizeof(self.bits) * 8 - self.len); uint bits = self.bits << padding; - uint n = (self.len + 7) / 8; + sz n = ((sz)self.len + 7) / 8; char[4] buffer; bitorder::write(bits, &buffer, UIntBE); io::write_all(self.writer, buffer[:n])!; @@ -68,15 +68,15 @@ fn void? BitWriter.flush(&self) } <* - @require nbits <= 32 + @require nbits <= 32 && nbits >= 0 *> -fn void? BitWriter.write_bits(&self, uint bits, uint nbits) +fn void? BitWriter.write_bits(&self, uint bits, int nbits) { - if (nbits == 0) return; + if (!nbits) return; while (self.len + nbits > WRITER_BITS) { - uint to_push = WRITER_BITS - self.len; - uint bits_to_push = (bits >> (nbits - to_push)) & ((1 << to_push) - 1); + int to_push = WRITER_BITS - self.len; + uint bits_to_push = (bits >> (nbits - to_push)) & ((1u << to_push) - 1u); self.bits <<= to_push; self.bits |= bits_to_push; @@ -86,9 +86,9 @@ fn void? BitWriter.write_bits(&self, uint bits, uint nbits) self.flush()!; } - if (nbits == 0) return; + if (!nbits) return; self.bits <<= nbits; - self.bits |= bits & ((1 << nbits) - 1); + self.bits |= bits & ((1u << nbits) - 1u); self.len += nbits; } \ No newline at end of file diff --git a/test/stdlib/src/io/file.c3 b/test/stdlib/src/io/file.c3 index d044976..bc2f82e 100644 --- a/test/stdlib/src/io/file.c3 +++ b/test/stdlib/src/io/file.c3 @@ -7,7 +7,7 @@ struct File (InStream, OutStream) } module std::io::file; -import libc, std::io::path, std::io::os; +import libc, std::io::path, std::io::os, std::time; fn File? open(String filename, String mode) { @@ -39,7 +39,7 @@ fn bool is_dir(String path) return os::native_is_dir(path); } -fn ulong? get_size(String path) +fn long? get_size(String path) { return os::native_file_size(path); } @@ -49,7 +49,13 @@ fn void? set_modified_time(String path, Time_t time) return os::native_set_modified_time(path, time); } -fn void? delete(String filename) +fn Time? last_modified(String filename) +{ + Time_t? seconds = os::native_get_modified_time(filename)!; + return (Time)(seconds * (Time_t)time::SEC); +} + +fn void? delete(String filename) @maydiscard { return os::native_remove(filename) @inline; } @@ -66,16 +72,7 @@ fn void? File.reopen(&self, String filename, String mode) <* @require self.file != null *> -fn usz? File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic -{ - os::native_fseek(self.file, offset, (SeekOrigin)seek_mode.ordinal)!; - return (usz)os::native_ftell(self.file); -} - -<* - @require self.file != null -*> -fn void? File.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic +fn void? File.seek(&self, long offset, SeekOrigin whence = FROM_START) @dynamic { return os::native_fseek(self.file, offset, whence); } @@ -115,34 +112,34 @@ fn void? File.write_byte(&self, char c) @dynamic <* @param [&inout] self *> -fn void? File.close(&self) @inline @dynamic +fn void? File.close(&self) @inline @dynamic @maydiscard { if (self.file && libc::fclose(self.file)) { switch (libc::errno()) { - case errno::ECONNRESET: - case errno::EBADF: return io::FILE_NOT_VALID~; - case errno::EINTR: return io::INTERRUPTED~; - case errno::EDQUOT: - case errno::EFAULT: - case errno::EAGAIN: - case errno::EFBIG: - case errno::ENETDOWN: - case errno::ENETUNREACH: - case errno::ENOSPC: - case errno::EIO: return io::INCOMPLETE_WRITE~; + case ECONNRESET: + case EBADF: return io::FILE_NOT_VALID~; + case EINTR: return io::INTERRUPTED~; + case EDQUOT: + case EFAULT: + case EAGAIN: + case EFBIG: + case ENETDOWN: + case ENETUNREACH: + case ENOSPC: + case EIO: return io::INCOMPLETE_WRITE~; default: return io::UNKNOWN_ERROR~; } } self.file = null; } -fn ulong? File.size(&self) @dynamic +fn long? File.size(&self) @dynamic { long curr = self.cursor()!; - defer (void)self.set_cursor(curr); - self.set_cursor(0, FROM_END)!; + defer (void)self.seek(curr); + self.seek(0, FROM_END)!; return self.cursor()!; } @@ -157,7 +154,7 @@ fn bool File.eof(&self) @inline <* @param [in] buffer *> -fn usz? File.read(&self, char[] buffer) @dynamic +fn sz? File.read(&self, char[] buffer) @dynamic { return os::native_fread(self.file, buffer); } @@ -166,7 +163,7 @@ fn usz? File.read(&self, char[] buffer) @dynamic @param [out] buffer @require self.file != null : `File must be initialized` *> -fn usz? File.write(&self, char[] buffer) @dynamic +fn sz? File.write(&self, char[] buffer) @dynamic { return os::native_fwrite(self.file, buffer); } @@ -201,7 +198,7 @@ fn char[]? load_buffer(String filename, char[] buffer) defer (void)file.close(); long len = file.size()!; if (len > buffer.len) return io::OVERFLOW~; - usz read = 0; + sz read = 0; while (read < len) { read += file.read(buffer[read:len - read])!; @@ -214,16 +211,16 @@ fn char[]? load(Allocator allocator, String filename) { File file = open(filename, "rb")!; defer (void)file.close(); - ulong len = file.size()!; - if (len > usz.max) return io::OUT_OF_SPACE~; - char* data = allocator::malloc_try(allocator, (usz)len)!; - defer catch allocator::free(allocator, data); - usz read = 0; - while (read < (usz)len) + long len = file.size()!; + if (len > sz::max) return io::OUT_OF_SPACE~; + char* data = alloc::malloc_try(allocator, (sz)len)!; + defer catch alloc::free(allocator, data); + sz read = 0; + while (read < (sz)len) { - read += file.read(data[read:(usz)len - read])!; + read += file.read(data[read:(sz)len - read])!; } - return data[:(usz)len]; + return data[:(sz)len]; } fn char[]? load_path(Allocator allocator, Path path) => load(allocator, path.str_view()); @@ -238,7 +235,7 @@ fn void? save(String filename, char[] data) defer (void)file.close(); while (data.len) { - usz written = file.write(data)!; + sz written = file.write(data)!; data = data[written..]; } } diff --git a/test/stdlib/src/io/file_mmap.c3 b/test/stdlib/src/io/file_mmap.c3 index 2671337..dfd9916 100644 --- a/test/stdlib/src/io/file_mmap.c3 +++ b/test/stdlib/src/io/file_mmap.c3 @@ -5,8 +5,8 @@ struct FileMmap { File file; VirtualMemory vm; - usz offset; - usz len; + sz offset; + sz len; } <* @@ -41,24 +41,24 @@ module std::io::file @if(env::LIBC &&& env::POSIX); @return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND @return "Memory mapped region. Must be released with FileMmap.destroy(). Provided File will not be closed" *> -fn FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false) +fn FileMmap? mmap_file(File file, sz offset = 0, sz len = 0, VirtualMemoryAccess access = READ, bool shared = false) { if (len == 0) { - ulong new_len = file.size()! - offset; - if (new_len > (ulong)isz.max) return mem::OUT_OF_MEMORY~; - len = (usz)new_len; + long new_len = file.size()! - offset; + if (new_len > sz::max) return mem::OUT_OF_MEMORY~; + len = (sz)new_len; } // get the page size - usz page_size = vm::aligned_alloc_size(0); + sz page_size = vm::aligned_alloc_size(0); // align the offset specified by the user (might be not aligned) - usz page_offset = offset & (page_size - 1); - usz map_offset = offset - page_offset; + sz page_offset = offset & (page_size - 1); + sz map_offset = offset - page_offset; // adjust map length (both the region start and the region end might be not aligned) - usz map_len = len + page_offset; // when region start not aligned + sz map_len = len + page_offset; // when region start not aligned map_len = vm::aligned_alloc_size(map_len); // when region end not aligned void* ptr = vm::mmap_file(file.fd(), map_len, map_offset, access, shared)!; @@ -77,7 +77,7 @@ fn FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, VirtualMemoryAcce @return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND @return "Memory mapped region. Must be released with FileMmap.destroy()" *> -fn FileMmap? mmap_open(String filename, String mode, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false) +fn FileMmap? mmap_open(String filename, String mode, sz offset = 0, sz len = 0, VirtualMemoryAccess access = READ, bool shared = false) { File file = open(filename, mode)!; defer catch (void)file.close(); diff --git a/test/stdlib/src/io/formatter.c3 b/test/stdlib/src/io/formatter.c3 index f899aed..ffda8d6 100644 --- a/test/stdlib/src/io/formatter.c3 +++ b/test/stdlib/src/io/formatter.c3 @@ -10,7 +10,7 @@ const int PRINTF_NTOA_BUFFER_SIZE = 256; interface Printable { fn String to_constant_string() @optional; - fn usz? to_format(Formatter* formatter) @optional; + fn sz? to_format(Formatter* formatter) @optional; } faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT, NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT; @@ -21,7 +21,7 @@ alias FloatType = double; macro bool is_struct_with_default_print($Type) { - return ($Type.kindof == STRUCT ||| $Type.kindof == BITSTRUCT) + return ($Type::kind == STRUCT ||| $Type::kind == BITSTRUCT) &&& !$defined($Type.to_format) &&& !$defined($Type.to_constant_string); } @@ -29,21 +29,21 @@ macro bool is_struct_with_default_print($Type) <* Introspect a struct and print it to a formatter - @require $kindof(value) == STRUCT || $kindof(value) == BITSTRUCT : `This macro is only valid on macros` + @require @kindof(value) == STRUCT || @kindof(value) == BITSTRUCT : `This macro is only valid on macros` *> -macro usz? struct_to_format(value, Formatter* f, bool $force_dump) +macro sz? struct_to_format(value, Formatter* f, bool $force_dump) { var $Type = $typeof(value); - usz total = f.print("{ ")!; - $foreach $i, $member : $Type.membersof: + sz total = f.print("{ ")!; + $foreach $i, $member : $Type::members: $if $i > 0: total += f.print(", ")!; $endif - $if $member.nameof != "": - total += f.printf("%s: ", $member.nameof)!; + $if $member.name != "": + total += f.printf("%s: ", $member.name)!; $endif - $if ($force_dump &&& ($member.typeid.kindof == STRUCT || $member.typeid.kindof == BITSTRUCT)) ||| - is_struct_with_default_print($member.typeid): + $if ($force_dump &&& ($member.kind == STRUCT || $member.kind == BITSTRUCT)) ||| + is_struct_with_default_print($member.type): total += struct_to_format($member.get(value), f, $force_dump)!; $else total += f.printf("%s", $member.get(value))!; @@ -52,12 +52,12 @@ macro usz? struct_to_format(value, Formatter* f, bool $force_dump) return total + f.print(" }"); } -fn usz? ReflectedParam.to_format(&self, Formatter* f) @dynamic +fn sz? ReflectedParam.to_format(&self, Formatter* f) @dynamic { return f.printf("[Parameter '%s']", self.name); } -fn usz? Formatter.printf(&self, String format, args...) +fn sz? Formatter.printf(&self, String format, args...) { return self.vprintf(format, args) @inline; } @@ -69,8 +69,8 @@ struct Formatter struct { PrintFlags flags; - uint width; - uint prec; + int width; + int prec; fault first_fault; } } @@ -93,13 +93,13 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null) -fn usz? Formatter.print_with_function(&self, Printable arg) +fn sz? Formatter.print_with_function(&self, Printable arg) { if (&arg.to_format) { PrintFlags old = self.flags; - uint old_width = self.width; - uint old_prec = self.prec; + int old_width = self.width; + int old_prec = self.prec; defer { self.flags = old; @@ -112,8 +112,8 @@ fn usz? Formatter.print_with_function(&self, Printable arg) if (&arg.to_constant_string) { PrintFlags old = self.flags; - uint old_width = self.width; - uint old_prec = self.prec; + int old_width = self.width; + int old_prec = self.prec; defer { self.flags = old; @@ -131,15 +131,15 @@ fn void? out_null_fn(void* data @unused, char c @unused) @private { } -macro usz? @report_fault(Formatter* f, $fault) +macro sz? @report_fault(Formatter* f, $fault) { (void)formatter_out_substr(f, $fault); return INVALID_FORMAT~; } -macro usz? @wrap_bad(Formatter* f, #action) +macro sz? @wrap_bad(Formatter* f, #action) { - usz? len = #action; + sz? len = #action; if (catch err = len) { switch (err) @@ -158,7 +158,7 @@ macro usz? @wrap_bad(Formatter* f, #action) -fn usz? Formatter.vprintf(&self, String format, any[] anys) +fn sz? Formatter.vprintf(&self, String format, any[] anys) { self.first_fault = {}; if (!self.out_fn) @@ -166,10 +166,10 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys) // use null output function self.out_fn = &out_null_fn; } - usz total_len; - usz format_len = format.len; - usz variant_index = 0; - for (usz i = 0; i < format_len; i++) + sz total_len; + sz format_len = format.len; + sz variant_index = 0; + for (sz i = 0; i < format_len; i++) { // format specifier? %[flags][width][.precision][length] char c = format[i]; @@ -212,7 +212,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys) self.flags.left = true; w = -w; } - self.width = (uint)w; + self.width = w; // evaluate precision field self.prec = 0; if (c == '.') @@ -221,7 +221,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys) if (++i >= format_len) return @report_fault(self, ""); int? prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i); if (catch prec) return @report_fault(self, ""); - self.prec = (uint)(prec < 0 ? 0 : prec); + self.prec = prec < 0 ? 0 : prec; c = format[i]; } @@ -315,9 +315,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys) return total_len; } -fn usz? Formatter.out(&self, char c) @deprecated("Use print_char") => self.print_char(c); - -fn usz? Formatter.print_char(&self, char c) +fn sz? Formatter.print_char(&self, char c) { if (catch err = self.out_fn(self.data, c)) { @@ -328,7 +326,7 @@ fn usz? Formatter.print_char(&self, char c) return 1; } -fn usz? Formatter.print(&self, String str) +fn sz? Formatter.print(&self, String str) { if (!self.out_fn) { diff --git a/test/stdlib/src/io/formatter_private.c3 b/test/stdlib/src/io/formatter_private.c3 index 3901c38..6c4f20f 100644 --- a/test/stdlib/src/io/formatter_private.c3 +++ b/test/stdlib/src/io/formatter_private.c3 @@ -6,10 +6,10 @@ const char[16] XDIGITS_L = "0123456789abcdef"; faultdef BAD_FORMAT; -fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline +fn sz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline { - char past_10 = (uppercase ? 'A' : 'a') - 10; - usz len = 0; + char past_10 = (uppercase ? 'A' : 'a') - 10u; + sz len = 0; foreach (c : out) { char digit = c >> 4; @@ -29,7 +29,7 @@ macro fault Formatter.first_err(&self, fault f) return f; } -fn usz? formatter_adjust(Formatter* f, usz len) @local +fn sz? formatter_adjust(Formatter* f, sz len) @local { if (!f.flags.left) return 0; return formatter_pad(f, ' ', f.width, len); @@ -37,7 +37,7 @@ fn usz? formatter_adjust(Formatter* f, usz len) @local fn uint128? int_from_any(any arg, bool *is_neg) @private { - switch (arg.type.kindof) + switch (arg.type.kind) { case FUNC: case POINTER: @@ -56,19 +56,19 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private return (uint128)*(bool*)arg; case ichar: int val = *(ichar*)arg; - return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val; + return (*is_neg = val < 0) ? (~(uint128)val) + 1u : (uint128)val; case short: int val = *(short*)arg; - return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val; + return (*is_neg = val < 0) ? (~(uint128)val) + 1u : (uint128)val; case int: int val = *(int*)arg; - return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val; + return (*is_neg = val < 0) ? (~(uint128)val) + 1u : (uint128)val; case long: long val = *(long*)arg; - return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val; + return (*is_neg = val < 0) ? (~(uint128)val) + 1u : (uint128)val; case int128: int128 val = *(int128*)arg; - return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val; + return (*is_neg = val < 0) ? (~(uint128)val) + 1u : (uint128)val; case char: return *(char*)arg; case ushort: @@ -93,9 +93,9 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private fn FloatType? float_from_any(any arg) @private { $if env::F128_SUPPORT: - if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr); + if (arg.type == float128::typeid) return (FloatType)*((float128*)arg.ptr); $endif - if (arg.type.kindof == TYPEDEF || arg.type.kindof == CONSTDEF) + if (arg.type.kind == TYPEDEF || arg.type.kind == CONSTDEF) { return float_from_any(arg.as_inner()); } @@ -141,10 +141,10 @@ fn FloatType? float_from_any(any arg) @private @param maxlen : "the maximum len that can be read." @return "The result of the atoi." *> -fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private +fn int simple_atoi(char* buf, sz maxlen, sz* len_ptr) @inline @private { - uint i = 0; - usz len = *len_ptr; + int i = 0; + sz len = *len_ptr; while (len < maxlen) { char c = buf[len]; @@ -156,13 +156,13 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private return i; } -fn usz? formatter_out_substr(Formatter* f, String str) @private +fn sz? formatter_out_substr(Formatter* f, String str) @private { - usz l = conv::utf8_codepoints(str); - uint prec = f.prec; + sz l = conv::utf8_codepoints(str); + int prec = f.prec; if (f.flags.precision && l < prec) l = prec; - usz index = 0; - usz chars = str.len; + sz index = 0; + sz chars = str.len; char* ptr = str.ptr; while (index < chars) { @@ -175,21 +175,21 @@ fn usz? formatter_out_substr(Formatter* f, String str) @private return index; } -fn usz? formatter_pad(Formatter* f, char c, isz width, isz len) @inline +fn sz? formatter_pad(Formatter* f, char c, sz width, sz len) @inline { - isz delta = width - len; - for (isz i = 0; i < delta; i++) f.print_char(c)!; + sz delta = width - len; + for (sz i = 0; i < delta; i++) f.print_char(c)!; return max(0, delta); } fn char* fmt_u(uint128 x, char* s) { - for (; x > ulong.max; x /= 10) *--s = '0' + (char)(x % 10); + for (; x > ulong::max; x /= 10) *--s = '0' + (char)(x % 10); for (ulong y = (ulong)x; y; y /= 10) *--s = '0' + (char)(y % 10); return s; } -fn usz? Formatter.out_chars(&self, char[] s) +fn sz? Formatter.out_chars(&self, char[] s) { foreach (c : s) self.print_char(c)!; return s.len; @@ -203,12 +203,12 @@ enum FloatFormatting HEX } -fn usz? formatter_etoa(Formatter* self, double y) => formatter_floatformat(self, EXPONENTIAL, y); -fn usz? formatter_ftoa(Formatter* self, double y) => formatter_floatformat(self, FLOAT, y); -fn usz? formatter_gtoa(Formatter* self, double y) => formatter_floatformat(self, ADAPTIVE, y); -fn usz? formatter_atoa(Formatter* self, double y) => formatter_floatformat(self, HEX, y); +fn sz? formatter_etoa(Formatter* self, double y) => formatter_floatformat(self, EXPONENTIAL, y); +fn sz? formatter_ftoa(Formatter* self, double y) => formatter_floatformat(self, FLOAT, y); +fn sz? formatter_gtoa(Formatter* self, double y) => formatter_floatformat(self, ADAPTIVE, y); +fn sz? formatter_atoa(Formatter* self, double y) => formatter_floatformat(self, HEX, y); -fn usz? formatter_floatformat_hex(Formatter* self, double y, bool is_neg, isz pl, isz p) @private @inline +fn sz? formatter_floatformat_hex(Formatter* self, double y, bool is_neg, sz pl, sz p) @private @inline { double round = 8.0; // 0x / 0X @@ -240,7 +240,7 @@ fn usz? formatter_floatformat_hex(Formatter* self, double y, bool is_neg, isz pl char* buf = &buf_array[0]; // Reverse print - char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf); + char* estr = fmt_u(e2 < 0 ? (uint128)-e2 : (uint128)e2, ebuf); if (estr == ebuf) *--estr = '0'; *--estr = (e2 < 0 ? '-' : '+'); *--estr = self.flags.uppercase ? 'P' : 'p'; @@ -252,27 +252,25 @@ fn usz? formatter_floatformat_hex(Formatter* self, double y, bool is_neg, isz pl *s++ = xdigits[x]; y = 16 * (y - x); if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.'; - if (p >= 0 && (s - buf) >= (2 + p)) break; + if (p >= 0 && (s - buf) >= (sz)(2 + p)) break; } while (y); - isz outlen = s - buf; - isz explen = ebuf - estr; - if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED~; - usz len; - usz l = (usz)(p && outlen - 2 < p - ? p + 2 + explen - : outlen + explen); + sz outlen = s - buf; + sz explen = ebuf - estr; + if (p > int::max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED~; + sz len; + sz l = (p && outlen - 2 < p ? explen + p + 2 : outlen + explen); if (!self.flags.left && !self.flags.zeropad) len += formatter_pad(self, ' ', self.width, pl + l)!; if (is_neg || self.flags.plus) len += self.print_char(is_neg ? '-' : '+')!; len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!; if (self.flags.zeropad) len += formatter_pad(self, '0', self.width, pl + l)!; len += self.out_chars(buf[:outlen])!; - len += formatter_pad(self, '0', (isz)l - outlen - explen, 0)!; + len += formatter_pad(self, '0', (sz)l - outlen - explen, 0)!; len += self.out_chars(estr[:explen])!; - if (self.flags.left) len += formatter_pad(self, ' ', self.width, pl + (isz)l)!; + if (self.flags.left) len += formatter_pad(self, ' ', self.width, pl + (sz)l)!; return len; } -fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, double y) @private +fn sz? formatter_floatformat(Formatter* self, FloatFormatting formatting, double y) @private { // This code is heavily based on musl's printf code const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1 @@ -284,11 +282,11 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl is_neg = true; y = -y; } - isz pl = is_neg || self.flags.plus ? 1 : 0; + sz pl = is_neg || self.flags.plus ? 1 : 0; // Print inf/nan if (!math::is_finite(y)) { - usz len; + sz len; // Add padding if (!self.flags.left) len += formatter_pad(self, ' ', self.width, 3 + pl)!; String s = self.flags.uppercase ? "INF" : "inf"; @@ -299,7 +297,7 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl return len; } - isz p = self.flags.precision ? self.prec : -1; + sz p = self.flags.precision ? self.prec : -1; if (formatting == HEX) return formatter_floatformat_hex(self, y, is_neg, pl, p); // Rescale @@ -353,9 +351,9 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl int need = (int)(1 + (p + math::DOUBLE_MANT_DIG / 3u + 8) / 9); for (uint* d = a; d < z; d++) { - uint rm = *d & ((1 << sh) - 1); + uint rm = *d & ((1u << sh) - 1u); *d = (*d >> sh) + carry; - carry = (1000000000 >> sh) * rm; + carry = (1000000000u >> sh) * rm; } if (!a[0]) a++; if (carry) z++[0] = carry; @@ -383,23 +381,23 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl uint* d = r + 1 + ((j + 9 * math::DOUBLE_MAX_EXP) / 9 - math::DOUBLE_MAX_EXP); j += 9 * math::DOUBLE_MAX_EXP; j %= 9; - int i; + uint i; for (i = 10, j++; j < 9; i *= 10, j++); - x = *d % (uint)i; + x = *d % i; // Are there any significant digits past j? if (x || (d + 1) != z) { double round = 2 / math::DOUBLE_EPSILON; double small; - if (((*d / (uint)i) & 1) || (i == 1000000000 && d > a && (d[-1] & 1))) + if (((*d / i) & 1u) || (i == 1000000000 && d > a && (d[-1] & 1u))) { round += 2; } switch { - case x < i / 2: + case x < i / 2u: small = 0x0.8p0; - case x == i / 2 && d + 1 == z: + case x == i / 2u && d + 1u == z: small = 0x1.0p0; default: small = 0x1.8p0; @@ -413,8 +411,8 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl // Decide whether to round by probing round+small if (round + small != round) { - *d = *d + i; - while (*d > 999999999) + *d = *d + (uint)i; + while (*d > 999999999u) { *d-- = 0; if (d < a) *--a = 0; @@ -433,7 +431,7 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl if (p > e && e >= -4) { formatting = FLOAT; - p -= (isz)e + 1; + p -= (sz)e + 1; } else { @@ -453,23 +451,23 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl } if (formatting == FLOAT) { - p = math::min(p, math::max((isz)0, 9 * (z - r - 1) - j)); + p = math::min(p, math::max((sz)0, 9 * (z - r - 1) - j)); } else { - p = math::min(p, math::max((isz)0, 9 * (z - r - 1) + e - j)); + p = math::min(p, math::max((sz)0, 9 * (z - r - 1) + e - j)); } } } - if (p > int.max - 1 - (isz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED~; - int l = (int)(1 + p + (isz)(p || self.flags.hash)); + if (p > int::max - 1 - (sz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED~; + int l = (int)(1 + p + (sz)(p || self.flags.hash)); char[12] ebuf0; char* ebuf = &ebuf0[0] + 12; char* estr @noinit; if (formatting == FLOAT) { - if (e > int.max - l) return INTERNAL_BUFFER_EXCEEDED~; + if (e > int::max - l) return INTERNAL_BUFFER_EXCEEDED~; if (e > 0) l += e; } else @@ -478,11 +476,11 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl while (ebuf - estr < 2) (--estr)[0] = '0'; *--estr = (e < 0 ? '-' : '+'); *--estr = self.flags.uppercase ? 'E' : 'e'; - if (ebuf - estr > (isz)int.max - l) return INTERNAL_BUFFER_EXCEEDED~; + if (ebuf - estr > (sz)int::max - l) return INTERNAL_BUFFER_EXCEEDED~; l += (int)(ebuf - estr); } - if (l > int.max - pl) return INTERNAL_BUFFER_EXCEEDED~; - usz len; + if (l > int::max - pl) return INTERNAL_BUFFER_EXCEEDED~; + sz len; if (!self.flags.left && !self.flags.zeropad) len += formatter_pad(self, ' ', self.width, pl + l)!; if (is_neg || self.flags.plus) len += self.print_char(is_neg ? '-' : '+')!; if (self.flags.zeropad) len += formatter_pad(self, '0', self.width, pl + l)!; @@ -511,7 +509,7 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl { char* s = fmt_u(*d, buf + 9); while (s > buf) *--s = '0'; - len += self.out_chars(s[:math::min((isz)9, p)])!; + len += self.out_chars(s[:math::min((sz)9, p)])!; } len += formatter_pad(self, '0', p + 9, 9)!; } @@ -544,22 +542,22 @@ fn usz? formatter_floatformat(Formatter* self, FloatFormatting formatting, doubl } -fn usz? formatter_out_str_pad(Formatter* self, any arg) @private @inline +fn sz? formatter_out_str_pad(Formatter* self, any arg) @private @inline { - usz total; + sz total; if (self.width && !self.flags.left) { OutputFn out_fn = self.out_fn; self.out_fn = (OutputFn)&out_null_fn; - usz len = formatter_out_str(self, arg)!; + sz len = formatter_out_str(self, arg)!; self.out_fn = out_fn; - total += formatter_pad(self, ' ', self.width, (isz)len)!; + total += formatter_pad(self, ' ', self.width, (sz)len)!; } - usz len = formatter_out_str(self, arg)!; + sz len = formatter_out_str(self, arg)!; total += len; if (self.flags.left) { - total += formatter_pad(self, ' ', self.width, (isz)len)!; + total += formatter_pad(self, ' ', self.width, (sz)len)!; } return total; } @@ -576,10 +574,10 @@ const char[201] DIGIT_PAIRS @private = "08182838485868788898" "09192939495969798999"; -fn usz? formatter_ntoa(Formatter* f, uint128 value, bool negative, uint base) @private +fn sz? formatter_ntoa(Formatter* f, uint128 value, bool negative, uint base) @private { char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit; - usz len; + sz len; // no hash for 0 values if (!value) f.flags.hash = false; @@ -587,14 +585,14 @@ fn usz? formatter_ntoa(Formatter* f, uint128 value, bool negative, uint base) @p // write if precision != 0 or value is != 0 if (!f.flags.precision || value) { - char past_10 = (f.flags.uppercase ? 'A' : 'a') - 10; + char past_10 = (f.flags.uppercase ? 'A' : 'a') - 10u; switch (base) { case 2: do { if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~; - buf[len++] = '0' + (char)value & 1; + buf[len++] = '0' + (char)value & 1u; value >>= 1; } while (value); @@ -641,7 +639,7 @@ fn usz? formatter_ntoa(Formatter* f, uint128 value, bool negative, uint base) @p return formatter_ntoa_format(f, (String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base); } -fn usz? formatter_ntoa_format(Formatter* f, String buf, usz len, bool negative, uint base) @private +fn sz? formatter_ntoa_format(Formatter* f, String buf, sz len, bool negative, uint base) @private { // pad leading zeros if (!f.flags.left) @@ -702,19 +700,19 @@ fn usz? formatter_ntoa_format(Formatter* f, String buf, usz len, bool negative, } -fn usz? formatter_ntoa_any(Formatter* f, any arg, uint base) @private +fn sz? formatter_ntoa_any(Formatter* f, any arg, uint base) @private { bool is_neg; return formatter_ntoa(f, int_from_any(arg, &is_neg)!!, is_neg, base) @inline; } -fn usz? formatter_out_char(Formatter* f, any arg) @private +fn sz? formatter_out_char(Formatter* f, any arg) @private { - if (!arg.type.kindof.is_int()) + if (!arg.type.kind.is_int()) { return formatter_out_substr(f, ""); } - usz len = 1; + sz len = 1; // pre padding if (!f.flags.left) { @@ -748,10 +746,10 @@ fn usz? formatter_out_char(Formatter* f, any arg) @private } -fn usz? formatter_out_reverse(Formatter* f, char[] buf) @private +fn sz? formatter_out_reverse(Formatter* f, char[] buf) @private { - usz n; - usz len = buf.len; + sz n; + sz len = buf.len; // pad spaces up to given width if (!f.flags.zeropad && !f.flags.left) { @@ -767,22 +765,21 @@ fn usz? formatter_out_reverse(Formatter* f, char[] buf) @private fn int? printf_parse_format_field( - any* args_ptr, usz args_len, usz* args_index_ptr, - char* format_ptr, usz format_len, usz* index_ptr) @inline @private + any* args_ptr, sz args_len, sz* args_index_ptr, + char* format_ptr, sz format_len, sz* index_ptr) @inline @private { char c = format_ptr[*index_ptr]; if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr); if (c != '*') return 0; - usz len = ++(*index_ptr); + sz len = ++(*index_ptr); if (len >= format_len) return BAD_FORMAT~; if (*args_index_ptr >= args_len) return BAD_FORMAT~; any val = args_ptr[(*args_index_ptr)++]; - if (!val.type.kindof.is_int()) return BAD_FORMAT~; - uint? intval = types::any_to_int(val, int); - return intval ?? BAD_FORMAT~; + if (!val.type.kind.is_int()) return BAD_FORMAT~; + return types::any_to_int(val, int) ?? BAD_FORMAT~; } -fn usz? formatter_out_hex_buffer(Formatter* self, any arg) @private @inline +fn sz? formatter_out_hex_buffer(Formatter* self, any arg) @private @inline { char[] out @noinit; switch (arg.type) @@ -791,55 +788,55 @@ fn usz? formatter_out_hex_buffer(Formatter* self, any arg) @private @inline case ichar[]: out = *(char[]*)arg; default: - if (arg.type.kindof == ARRAY && (arg.type.inner == char.typeid || arg.type.inner == ichar.typeid)) + if (arg.type.kind == ARRAY && (arg.type.inner == char::typeid || arg.type.inner == ichar::typeid)) { - out = ((char*)arg.ptr)[:arg.type.sizeof]; + out = ((char*)arg.ptr)[:arg.type.size]; break; } - if (arg.type.kindof == POINTER) + if (arg.type.kind == POINTER) { // Maybe there is a more idiomatic way here - out = ((*(char**)arg.ptr))[:arg.type.inner.sizeof]; + out = ((*(char**)arg.ptr))[:arg.type.inner.size]; break; } return formatter_out_substr(self, ""); } - usz len = out.len * 2; - usz total; + sz len = out.len * 2; + sz total; if (self.flags.left) { total += print_hex_chars(self, out, self.flags.uppercase)!; - total += formatter_pad(self, ' ', self.width, (isz)total)!; + total += formatter_pad(self, ' ', self.width, total)!; } else { - if (self.width) total += formatter_pad(self, ' ', self.width, (isz)len)!; + if (self.width) total += formatter_pad(self, ' ', self.width, len)!; total += print_hex_chars(self, out, self.flags.uppercase)!; } return total; } -fn usz? formatter_out_unknown(Formatter* self, String category, any arg) @private +fn sz? formatter_out_unknown(Formatter* self, String category, any arg) @private { return formatter_out_substr(self, "<") + formatter_out_substr(self, category) + formatter_out_substr(self, " type:") - + formatter_ntoa(self, (iptr)arg.type, false, 16) + + formatter_ntoa(self, (uptr)arg.type, false, 16) + formatter_out_substr(self, ", addr:") - + formatter_ntoa(self, (iptr)arg.ptr, false, 16) + + formatter_ntoa(self, (uptr)arg.ptr, false, 16) + formatter_out_substr(self, ">"); } -fn usz? formatter_out_collection(Formatter* self, any arg, String open, String close) @private +fn sz? formatter_out_collection(Formatter* self, any arg, String open, String close) @private { typeid inner = arg.type.inner; - if (inner == void.typeid) inner = char.typeid; - usz size = inner.sizeof; + if (inner == void::typeid) inner = char::typeid; + sz size = inner.size; - usz alen; + sz alen; void* data_ptr; - if (arg.type.kindof == SLICE) + if (arg.type.kind == SLICE) { String* temp = arg.ptr; data_ptr = temp.ptr; @@ -852,7 +849,7 @@ fn usz? formatter_out_collection(Formatter* self, any arg, String open, String c } PrintFlags flags = self.flags; - uint width = self.width; + int width = self.width; defer { self.flags = flags; @@ -861,8 +858,8 @@ fn usz? formatter_out_collection(Formatter* self, any arg, String open, String c self.flags = {}; self.width = 0; - usz len = formatter_out_substr(self, open)!; - for (usz i = 0; i < alen; i++) + sz len = formatter_out_substr(self, open)!; + for (sz i = 0; i < alen; i++) { if (i != 0) len += formatter_out_substr(self, ", ")!; len += formatter_out_str(self, any_make(data_ptr, inner))!; @@ -872,15 +869,15 @@ fn usz? formatter_out_collection(Formatter* self, any arg, String open, String c return len; } -fn usz? formatter_out_str(Formatter* self, any arg) @private +fn sz? formatter_out_str(Formatter* self, any arg) @private { - switch (arg.type.kindof) + switch (arg.type.kind) { case VOID: return formatter_out_substr(self, "void"); case FAULT: fault f = *(fault*)arg.ptr; - return formatter_out_substr(self, f ? f.nameof : "(empty-fault)"); + return formatter_out_substr(self, f ? f.description : "(empty-fault)"); case INTERFACE: any a = *(any*)arg; return a ? formatter_out_str(self, a) : formatter_out_substr(self, "(empty-interface)"); @@ -895,7 +892,7 @@ fn usz? formatter_out_str(Formatter* self, any arg) @private case FUNC: case POINTER: PrintFlags flags = self.flags; - uint width = self.width; + int width = self.width; defer { self.flags = flags; @@ -903,7 +900,7 @@ fn usz? formatter_out_str(Formatter* self, any arg) @private } self.flags = {}; self.width = 0; - switch (arg.type.kindof) + switch (arg.type.kind) { case SIGNED_INT: case UNSIGNED_INT: @@ -912,11 +909,11 @@ fn usz? formatter_out_str(Formatter* self, any arg) @private return formatter_ftoa(self, float_from_any(arg)) ?? formatter_out_substr(self, "ERR"); case FUNC: case POINTER: - if (arg.type.kindof == POINTER && arg.type.inner != void.typeid) + if (arg.type.kind == POINTER && arg.type.inner != void::typeid) { void** pointer = arg.ptr; any deref = any_make(*pointer, arg.type.inner); - usz? n = self.print_with_function((Printable)deref); + sz? n = self.print_with_function((Printable)deref); if (try n) return n; if (@catch(n) != NOT_FOUND) n!; } @@ -928,15 +925,15 @@ fn usz? formatter_out_str(Formatter* self, any arg) @private return formatter_out_substr(self, *(bool*)arg.ptr ? "true" : "false"); default: } - usz? n = self.print_with_function((Printable)arg); + sz? n = self.print_with_function((Printable)arg); if (try n) return n; if (@catch(n) != NOT_FOUND) n!; - switch (arg.type.kindof) + switch (arg.type.kind) { case TYPEID: - return formatter_out_substr(self, "typeid[")! + formatter_ntoa(self, (iptr)*(typeid*)arg, false, 16)! + formatter_out_substr(self, "]")!; + return formatter_out_substr(self, "typeid[")! + formatter_ntoa(self, (uptr)*(typeid*)arg, false, 16)! + formatter_out_substr(self, "]")!; case ENUM: - usz i = types::any_to_enum_ordinal(arg, usz)!!; + sz i = types::any_to_enum_ordinal(arg, sz)!!; assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i); return formatter_out_substr(self, arg.type.names[i]); case STRUCT: @@ -947,15 +944,15 @@ fn usz? formatter_out_str(Formatter* self, any arg) @private return formatter_out_unknown(self, "bitstruct", arg); case CONSTDEF: case TYPEDEF: - if (arg.type == String.typeid) + if (arg.type == String::typeid) { return formatter_out_substr(self, *(String*)arg); } - if (arg.type == ZString.typeid) + if (arg.type == ZString::typeid) { return formatter_out_substr(self, *(ZString*)arg ? ((ZString*)arg).str_view() : "(null)"); } - if (arg.type == DString.typeid) + if (arg.type == DString::typeid) { return formatter_out_substr(self, *(DString*)arg ? ((DString*)arg).str_view() : "(null)"); } diff --git a/test/stdlib/src/io/io.c3 b/test/stdlib/src/io/io.c3 index e09ab8d..0b9f175 100644 --- a/test/stdlib/src/io/io.c3 +++ b/test/stdlib/src/io/io.c3 @@ -4,13 +4,6 @@ module std::io; import libc; -enum Seek -{ - SET, - CURSOR, - END -} - enum SeekOrigin { FROM_START, @@ -66,7 +59,7 @@ faultdef @param [inout] allocator : `the allocator to use.` @return `The string containing the data read.` *> -macro String? readline(Allocator allocator, stream = io::stdin(), usz limit = 0) +macro String? readline(Allocator allocator, stream = io::stdin(), sz limit = 0) { return readline_impl{$typeof(stream)}(allocator, stream, limit); } @@ -81,12 +74,12 @@ macro String? readline(Allocator allocator, stream = io::stdin(), usz limit = 0) @require @is_instream(stream) : `The stream must implement InStream.` @return `The temporary string containing the data read.` *> -macro String? treadline(stream = io::stdin(), usz limit = 0) +macro String? treadline(stream = io::stdin(), sz limit = 0) { return readline(tmem, stream, limit) @inline; } -fn String? readline_impl(Allocator allocator, Stream stream, usz limit) @private +fn String? readline_impl(Allocator allocator, Stream stream, sz limit) @private { if (allocator == tmem) { @@ -114,12 +107,12 @@ fn String? readline_impl(Allocator allocator, Stream stream, usz limit) @require @is_outstream(out_stream) : `The out_stream must implement OutStream.` @return `The number of bytes written. When a 'limit' is provided and the return value is equal to it, there may be more to read on the current line.` *> -macro usz? readline_to_stream(out_stream, in_stream = io::stdin(), usz limit = 0) +macro sz? readline_to_stream(out_stream, in_stream = io::stdin(), sz limit = 0) { return readline_to_stream_impl{$typeof(in_stream), $typeof(out_stream)}(out_stream, in_stream, limit); } -fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream, usz limit) @private +fn sz? readline_to_stream_impl(OStream out_stream, IStream in_stream, sz limit) @private { bool $is_stream = IStream == InStream; $if $is_stream: @@ -134,7 +127,7 @@ fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream, usz limit $endif if (val == '\n') return 0; - usz len; + sz len; if (val != '\r') { $if $is_out_stream: @@ -175,7 +168,7 @@ fn usz? readline_to_stream_impl(OStream out_stream, IStream in_stream, usz limit @require @is_outstream(out) : `The output must implement OutStream.` @return `the number of bytes printed.` *> -macro usz? fprint(out, x) +macro sz? fprint(out, x) { var $Type = $typeof(x); $switch $Type: @@ -205,7 +198,7 @@ macro usz? fprint(out, x) @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? fprintf(OutStream out, String format, args...) @format(1) +fn sz? fprintf(OutStream out, String format, args...) @format(1) { Formatter formatter; formatter.init(&out_putstream_fn, &out); @@ -220,11 +213,11 @@ fn usz? fprintf(OutStream out, String format, args...) @format(1) @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard +fn sz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard { Formatter formatter; formatter.init(&out_putstream_fn, &out); - usz len = formatter.vprintf(format, args)!; + sz len = formatter.vprintf(format, args)!; out.write_byte('\n')!; if (&out.flush) out.flush()!; return len + 1; @@ -233,9 +226,9 @@ fn usz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard <* @require @is_outstream(out) : "The output must implement OutStream" *> -macro usz? fprintn(out, x = "") +macro sz? fprintn(out, x = "") { - usz len = fprint(out, x)!; + sz len = fprint(out, x)!; out.write_byte('\n')!; $switch: $case $typeof(out) == OutStream: @@ -289,7 +282,7 @@ fn void? out_putstream_fn(void* data, char c) @private return (*stream).write_byte(c); } -macro usz putchar_buf_size() @const +macro sz putchar_buf_size() @const { $switch env::MEMORY_ENV: $case NORMAL: return 32 * 1024; @@ -302,14 +295,14 @@ macro usz putchar_buf_size() @const struct PutcharBuffer { char[putchar_buf_size()] data; - usz len; + sz len; bool should_flush; } fn void? write_putchar_buffer(PutcharBuffer* buff, bool flush) @private { File* stdout = io::stdout(); - libc::fwrite(&buff.data, 1, buff.len, stdout.file); + libc::fwrite(&buff.data, 1, (usz)buff.len, stdout.file); buff.len = 0; if (flush) stdout.flush()!; } @@ -343,12 +336,12 @@ fn void? out_putchar_buffer_fn(void* data @unused, char c) @private @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? printf(String format, args...) @format(0) @maydiscard +fn sz? printf(String format, args...) @format(0) @maydiscard { Formatter formatter; PutcharBuffer buff; formatter.init(&out_putchar_buffer_fn, &buff); - usz? len = formatter.vprintf(format, args); + sz? len = formatter.vprintf(format, args); write_putchar_buffer(&buff, buff.should_flush)!; return len; } @@ -360,12 +353,12 @@ fn usz? printf(String format, args...) @format(0) @maydiscard @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? printfn(String format, args...) @format(0) @maydiscard +fn sz? printfn(String format, args...) @format(0) @maydiscard { Formatter formatter; PutcharBuffer buff; formatter.init(&out_putchar_buffer_fn, &buff); - usz? len = formatter.vprintf(format, args); + sz? len = formatter.vprintf(format, args); formatter.print_char('\n')!; write_putchar_buffer(&buff, true)!; return len + 1; @@ -378,7 +371,7 @@ fn usz? printfn(String format, args...) @format(0) @maydiscard @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? eprintf(String format, args...) @maydiscard +fn sz? eprintf(String format, args...) @maydiscard { Formatter formatter; OutStream stream = stderr(); @@ -394,12 +387,12 @@ fn usz? eprintf(String format, args...) @maydiscard @param [in] format : `The printf-style format string` @return `the number of characters printed` *> -fn usz? eprintfn(String format, args...) @maydiscard +fn sz? eprintfn(String format, args...) @maydiscard { Formatter formatter; OutStream stream = stderr(); formatter.init(&out_putstream_fn, &stream); - usz? len = formatter.vprintf(format, args); + sz? len = formatter.vprintf(format, args); stderr().write_byte('\n')!; stderr().flush()!; return len + 1; @@ -418,7 +411,7 @@ fn char[]? bprintf(char[] buffer, String format, args...) @maydiscard Formatter formatter; BufferData data = { .buffer = buffer }; formatter.init(&out_buffer_fn, &data); - usz size = formatter.vprintf(format, args)!; + sz size = formatter.vprintf(format, args)!; return buffer[:data.written]; } @@ -434,7 +427,7 @@ fn void? out_buffer_fn(void *data, char c) @private struct BufferData @private { char[] buffer; - usz written; + sz written; } // Only available with LIBC diff --git a/test/stdlib/src/io/os/chdir.c3 b/test/stdlib/src/io/os/chdir.c3 index 819093a..c1cfd35 100644 --- a/test/stdlib/src/io/os/chdir.c3 +++ b/test/stdlib/src/io/os/chdir.c3 @@ -11,12 +11,12 @@ macro void? native_chdir(Path path) { switch (libc::errno()) { - case errno::EACCES: return io::NO_PERMISSION~; - case errno::ENAMETOOLONG: return io::NAME_TOO_LONG~; - case errno::ENOTDIR: return io::FILE_NOT_DIR~; - case errno::ENOENT: return io::FILE_NOT_FOUND~; - case errno::ELOOP: return io::SYMLINK_FAILED~; - default: return io::GENERAL_ERROR~; + case EACCES: return io::NO_PERMISSION~; + case ENAMETOOLONG: return io::NAME_TOO_LONG~; + case ENOTDIR: return io::FILE_NOT_DIR~; + case ENOENT: return io::FILE_NOT_FOUND~; + case ELOOP: return io::SYMLINK_FAILED~; + default: return io::GENERAL_ERROR~; } } $case env::WIN32: diff --git a/test/stdlib/src/io/os/file_libc.c3 b/test/stdlib/src/io/os/file_libc.c3 index 9e8996d..b82d286 100644 --- a/test/stdlib/src/io/os/file_libc.c3 +++ b/test/stdlib/src/io/os/file_libc.c3 @@ -26,11 +26,9 @@ fn void? native_remove(String filename) => @stack_mem(256; Allocator smem) { switch (libc::errno()) { - case errno::ENOENT: - return io::FILE_NOT_FOUND~; - case errno::EACCES: - default: - return io::FILE_CANNOT_DELETE~; + case ENOENT: return io::FILE_NOT_FOUND~; + case EACCES: + default: return io::FILE_CANNOT_DELETE~; } } } @@ -61,9 +59,9 @@ fn long? native_ftell(CFile file) @inline return index >= 0 ? index : file_seek_errno()~; } -fn usz? native_fwrite(CFile file, char[] buffer) @inline +fn sz? native_fwrite(CFile file, char[] buffer) @inline { - return libc::fwrite(buffer.ptr, 1, buffer.len, file); + return (sz)libc::fwrite(buffer.ptr, 1u, (usz)buffer.len, file); } fn void? native_fputc(CInt c, CFile stream) @inline @@ -71,9 +69,9 @@ fn void? native_fputc(CInt c, CFile stream) @inline if (libc::fputc(c, stream) == libc::EOF) return io::EOF~; } -fn usz? native_fread(CFile file, char[] buffer) @inline +fn sz? native_fread(CFile file, char[] buffer) @inline { - return libc::fread(buffer.ptr, 1, buffer.len, file); + return (sz)libc::fread(buffer.ptr, 1u, (usz)buffer.len, file); } fn void? native_fflush(CFile file) @inline @maydiscard @@ -85,26 +83,26 @@ macro fault file_open_errno() @local { switch (libc::errno()) { - case errno::EACCES: return io::NO_PERMISSION; - case errno::EDQUOT: return io::OUT_OF_SPACE; - case errno::EBADF: return io::FILE_NOT_VALID; - case errno::EEXIST: return io::ALREADY_EXISTS; - case errno::EINTR: return io::INTERRUPTED; - case errno::EFAULT: return io::GENERAL_ERROR; - case errno::EISDIR: return io::FILE_IS_DIR; - case errno::ELOOP: return io::SYMLINK_FAILED; - case errno::EMFILE: return io::TOO_MANY_DESCRIPTORS; - case errno::ENAMETOOLONG: return io::NAME_TOO_LONG; - case errno::ENFILE: return io::OUT_OF_SPACE; - case errno::ENOTDIR: return io::FILE_NOT_DIR; - case errno::ENOENT: return io::FILE_NOT_FOUND; - case errno::ENOSPC: return io::OUT_OF_SPACE; - case errno::ENXIO: return io::FILE_NOT_FOUND; - case errno::EOVERFLOW: return io::OVERFLOW; - case errno::EROFS: return io::READ_ONLY; - case errno::EOPNOTSUPP: return io::UNSUPPORTED_OPERATION; - case errno::EIO: return io::INCOMPLETE_WRITE; - case errno::EWOULDBLOCK: return io::WOULD_BLOCK; + case EACCES: return io::NO_PERMISSION; + case EDQUOT: return io::OUT_OF_SPACE; + case EBADF: return io::FILE_NOT_VALID; + case EEXIST: return io::ALREADY_EXISTS; + case EINTR: return io::INTERRUPTED; + case EFAULT: return io::GENERAL_ERROR; + case EISDIR: return io::FILE_IS_DIR; + case ELOOP: return io::SYMLINK_FAILED; + case EMFILE: return io::TOO_MANY_DESCRIPTORS; + case ENAMETOOLONG: return io::NAME_TOO_LONG; + case ENFILE: return io::OUT_OF_SPACE; + case ENOTDIR: return io::FILE_NOT_DIR; + case ENOENT: return io::FILE_NOT_FOUND; + case ENOSPC: return io::OUT_OF_SPACE; + case ENXIO: return io::FILE_NOT_FOUND; + case EOVERFLOW: return io::OVERFLOW; + case EROFS: return io::READ_ONLY; + case EOPNOTSUPP: return io::UNSUPPORTED_OPERATION; + case EIO: return io::INCOMPLETE_WRITE; + case EWOULDBLOCK: return io::WOULD_BLOCK; default: return io::UNKNOWN_ERROR; } } @@ -113,17 +111,17 @@ macro fault file_seek_errno() @local { switch (libc::errno()) { - case errno::ESPIPE: return io::FILE_IS_PIPE; - case errno::EPIPE: return io::FILE_IS_PIPE; - case errno::EOVERFLOW: return io::OVERFLOW; - case errno::ENXIO: return io::FILE_NOT_FOUND; - case errno::ENOSPC: return io::OUT_OF_SPACE; - case errno::EIO: return io::INCOMPLETE_WRITE; - case errno::EINVAL: return io::INVALID_POSITION; - case errno::EINTR: return io::INTERRUPTED; - case errno::EFBIG: return io::OUT_OF_SPACE; - case errno::EBADF: return io::FILE_NOT_VALID; - case errno::EAGAIN: return io::WOULD_BLOCK; + case ESPIPE: return io::FILE_IS_PIPE; + case EPIPE: return io::FILE_IS_PIPE; + case EOVERFLOW: return io::OVERFLOW; + case ENXIO: return io::FILE_NOT_FOUND; + case ENOSPC: return io::OUT_OF_SPACE; + case EIO: return io::INCOMPLETE_WRITE; + case EINVAL: return io::INVALID_POSITION; + case EINTR: return io::INTERRUPTED; + case EFBIG: return io::OUT_OF_SPACE; + case EBADF: return io::FILE_NOT_VALID; + case EAGAIN: return io::WOULD_BLOCK; default: return io::UNKNOWN_ERROR; } } diff --git a/test/stdlib/src/io/os/file_nolibc.c3 b/test/stdlib/src/io/os/file_nolibc.c3 index e53fe81..6c19c14 100644 --- a/test/stdlib/src/io/os/file_nolibc.c3 +++ b/test/stdlib/src/io/os/file_nolibc.c3 @@ -6,8 +6,8 @@ alias FreopenFn = fn void*?(void*, String, String); alias FcloseFn = fn void?(void*); alias FseekFn = fn void?(void*, long, SeekOrigin); alias FtellFn = fn long?(void*); -alias FwriteFn = fn usz?(void*, char[] buffer); -alias FreadFn = fn usz?(void*, char[] buffer); +alias FwriteFn = fn sz?(void*, char[] buffer); +alias FreadFn = fn sz?(void*, char[] buffer); alias FflushFn = fn void?(void*); alias RemoveFn = fn void?(String); alias FputcFn = fn void?(int, void*); @@ -62,19 +62,19 @@ fn void? native_fseek(void* file, long offset, SeekOrigin whence) @inline return io::UNSUPPORTED_OPERATION~; } -fn ulong? native_ftell(CFile file) @inline +fn long? native_ftell(CFile file) @inline { if (native_ftell_fn) return native_ftell_fn(file); return io::UNSUPPORTED_OPERATION~; } -fn usz? native_fwrite(CFile file, char[] buffer) @inline +fn sz? native_fwrite(CFile file, char[] buffer) @inline { if (native_fwrite_fn) return native_fwrite_fn(file, buffer); return io::UNSUPPORTED_OPERATION~; } -fn usz? native_fread(CFile file, char[] buffer) @inline +fn sz? native_fread(CFile file, char[] buffer) @inline { if (native_fread_fn) return native_fread_fn(file, buffer); return io::UNSUPPORTED_OPERATION~; diff --git a/test/stdlib/src/io/os/fileinfo.c3 b/test/stdlib/src/io/os/fileinfo.c3 index 347119f..05a91a2 100644 --- a/test/stdlib/src/io/os/fileinfo.c3 +++ b/test/stdlib/src/io/os/fileinfo.c3 @@ -13,49 +13,39 @@ fn void? native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || e { switch (libc::errno()) { - case errno::EBADF: - return io::FILE_NOT_VALID~; - case errno::EFAULT: - unreachable("Invalid stat"); - case errno::EIO: - return io::GENERAL_ERROR~; - case errno::EACCES: - return io::NO_PERMISSION~; - case errno::ELOOP: - return io::NO_PERMISSION~; - case errno::ENAMETOOLONG: - return io::NAME_TOO_LONG~; - case errno::ENOENT: - return io::FILE_NOT_FOUND~; - case errno::ENOTDIR: - return io::FILE_NOT_DIR~; - case errno::EOVERFLOW: - return io::GENERAL_ERROR~; - default: - return io::UNKNOWN_ERROR~; + case EBADF: return io::FILE_NOT_VALID~; + case EFAULT: unreachable("Invalid stat"); + case EIO: return io::GENERAL_ERROR~; + case EACCES: return io::NO_PERMISSION~; + case ELOOP: return io::NO_PERMISSION~; + case ENAMETOOLONG: return io::NAME_TOO_LONG~; + case ENOENT: return io::FILE_NOT_FOUND~; + case ENOTDIR: return io::FILE_NOT_DIR~; + case EOVERFLOW: return io::GENERAL_ERROR~; + default: return io::UNKNOWN_ERROR~; } } } -fn usz? native_file_size(String path) @if(env::WIN32) => @pool() +fn long? native_file_size(String path) @if(env::WIN32) => @pool() { Win32_FILE_ATTRIBUTE_DATA data; win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data); Win32_LARGE_INTEGER size; - size.lowPart = data.nFileSizeLow; - size.highPart = data.nFileSizeHigh; - return (usz)size.quadPart; + size.lowPart = (Win32_DWORD)data.nFileSizeLow; + size.highPart = (Win32_LONG)data.nFileSizeHigh; + return (long)size.quadPart; } -fn ulong? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN && !env::LINUX && !env::ANDROID && !env::BSD_FAMILY) +fn long? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN && !env::LINUX && !env::ANDROID && !env::BSD_FAMILY) { File f = file::open(path, "r")!; defer (void)f.close(); - f.set_cursor(0, FROM_END)!; + f.seek(0, FROM_END)!; return f.cursor(); } -fn ulong? native_file_size(String path) @if(env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY) +fn long? native_file_size(String path) @if(env::DARWIN || env::LINUX || env::ANDROID || env::BSD_FAMILY) { Stat stat; native_stat(&stat, path)!; @@ -116,4 +106,32 @@ fn bool native_is_dir(String path) $endif } - +fn Time_t? native_get_modified_time(String path) => @pool() +{ + $switch: + $case env::WIN32: + Win32_FILE_ATTRIBUTE_DATA data; + if (!win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data)) + { + return io::FILE_NOT_FOUND~; + }; + ulong ticks = ((ulong)data.ftLastWriteTime.dwHighDateTime << 32) | data.ftLastWriteTime.dwLowDateTime; + return (Time_t)(ticks / 10_000_000UL - 11644473600UL); + $case env::DARWIN: + Stat stat; + native_stat(&stat, path)!; + return stat.st_mtimespec.s; + $case env::LINUX: + $case env::ANDROID: + $case env::NETBSD: + Stat stat; + native_stat(&stat, path)!; + return stat.st_mtime; + $case env::BSD_FAMILY: + Stat stat; + native_stat(&stat, path)!; + return stat.st_mtime.s; + $default: + return io::UNSUPPORTED_OPERATION~; + $endswitch +} diff --git a/test/stdlib/src/io/os/getcwd.c3 b/test/stdlib/src/io/os/getcwd.c3 index 7bcdedd..a8f5dd7 100644 --- a/test/stdlib/src/io/os/getcwd.c3 +++ b/test/stdlib/src/io/os/getcwd.c3 @@ -12,7 +12,7 @@ macro String? getcwd(Allocator allocator) defer if (free) libc::free(res); if (!res) { - if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR~; + if (libc::errno() != ERANGE) return io::GENERAL_ERROR~; res = win32::_wgetcwd(null, 0); free = true; } @@ -20,14 +20,14 @@ macro String? getcwd(Allocator allocator) return string::from_utf16(allocator, str16); $case env::POSIX: - const usz DEFAULT_BUFFER = 256; + const sz DEFAULT_BUFFER = 256; char[DEFAULT_BUFFER] buffer; ZString res = posix::getcwd(&buffer, DEFAULT_BUFFER); bool free = false; if (!res) { // Improve error - if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR~; + if (libc::errno() != ERANGE) return io::GENERAL_ERROR~; res = posix::getcwd(null, 0); free = true; } diff --git a/test/stdlib/src/io/os/mkdir.c3 b/test/stdlib/src/io/os/mkdir.c3 index ca85e9c..79434a9 100644 --- a/test/stdlib/src/io/os/mkdir.c3 +++ b/test/stdlib/src/io/os/mkdir.c3 @@ -14,18 +14,18 @@ macro bool? native_mkdir(Path path, MkdirPermissions permissions) if (!posix::mkdir(path.str_view().zstr_tcopy(), permissions == NORMAL ? 0o777 : 0o700)) return true; switch (libc::errno()) { - case errno::EACCES: - case errno::EPERM: - case errno::EROFS: - case errno::EFAULT: return io::NO_PERMISSION~; - case errno::ENAMETOOLONG: return io::NAME_TOO_LONG~; - case errno::EDQUOT: - case errno::ENOSPC: return io::OUT_OF_SPACE~; - case errno::EISDIR: - case errno::EEXIST: return false; - case errno::ELOOP: return io::SYMLINK_FAILED~; - case errno::ENOTDIR: return io::FILE_NOT_FOUND~; - case errno::ENOENT: return io::PARENT_DIR_MISSING~; + case EACCES: + case EPERM: + case EROFS: + case EFAULT: return io::NO_PERMISSION~; + case ENAMETOOLONG: return io::NAME_TOO_LONG~; + case EDQUOT: + case ENOSPC: return io::OUT_OF_SPACE~; + case EISDIR: + case EEXIST: return false; + case ELOOP: return io::SYMLINK_FAILED~; + case ENOTDIR: return io::FILE_NOT_FOUND~; + case ENOENT: return io::PARENT_DIR_MISSING~; default: return io::GENERAL_ERROR~; } diff --git a/test/stdlib/src/io/os/rmdir.c3 b/test/stdlib/src/io/os/rmdir.c3 index c0d5560..965b2e8 100644 --- a/test/stdlib/src/io/os/rmdir.c3 +++ b/test/stdlib/src/io/os/rmdir.c3 @@ -13,16 +13,16 @@ macro bool? native_rmdir(Path path) if (!posix::rmdir(path.str_view().zstr_tcopy())) return true; switch (libc::errno()) { - case errno::EBUSY: return io::BUSY~; - case errno::EACCES: - case errno::EPERM: - case errno::EROFS: - case errno::EFAULT: return io::NO_PERMISSION~; - case errno::ENAMETOOLONG: return io::NAME_TOO_LONG~; - case errno::ENOTDIR: - case errno::ENOENT: return false; - case errno::ENOTEMPTY: return io::DIR_NOT_EMPTY~; - case errno::ELOOP: return io::SYMLINK_FAILED~; + case EBUSY: return io::BUSY~; + case EACCES: + case EPERM: + case EROFS: + case EFAULT: return io::NO_PERMISSION~; + case ENAMETOOLONG: return io::NAME_TOO_LONG~; + case ENOTDIR: + case ENOENT: return false; + case ENOTEMPTY: return io::DIR_NOT_EMPTY~; + case ELOOP: return io::SYMLINK_FAILED~; default: return io::GENERAL_ERROR~; } $case env::WIN32: diff --git a/test/stdlib/src/io/path.c3 b/test/stdlib/src/io/path.c3 index d17082e..c8d1b14 100644 --- a/test/stdlib/src/io/path.c3 +++ b/test/stdlib/src/io/path.c3 @@ -36,7 +36,7 @@ fn Path? cwd(Allocator allocator) fn bool is_dir(Path path) => os::native_is_dir(path.str_view()); fn bool is_file(Path path) => os::native_is_file(path.str_view()); -fn ulong? file_size(Path path) => os::native_file_size(path.str_view()); +fn long? file_size(Path path) => os::native_file_size(path.str_view()); fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view()); fn Path? tcwd() => cwd(tmem) @inline; @@ -56,7 +56,6 @@ macro void? chdir(path) } fn Path? temp_directory(Allocator allocator) => os::native_temp_directory(allocator); - fn Path? home_directory(Allocator allocator) => os::native_home_directory(allocator); fn Path? desktop_directory(Allocator allocator) => os::native_user_directory(allocator, DESKTOP); fn Path? videos_directory(Allocator allocator) => os::native_user_directory(allocator, VIDEOS); @@ -167,8 +166,6 @@ fn Path? from_wstring(Allocator allocator, WString path) => @pool() return path::new(allocator, string::tfrom_wstring(path)!); } -fn Path? from_win32_wstring(Allocator allocator, WString path) @deprecated("Use 'from_wstring' instead") => from_wstring(allocator, path); - fn Path? for_windows(Allocator allocator, String path) => new(allocator, path, WIN32); fn Path? for_posix(Allocator allocator, String path) => new(allocator, path, POSIX); @@ -198,11 +195,11 @@ fn Path? Path.append(self, Allocator allocator, String filename) fn Path? Path.tappend(self, String filename) => self.append(tmem, filename); -fn usz? start_of_base_name(String str, PathEnv path_env) @local +fn sz? start_of_base_name(String str, PathEnv path_env) @local { if (!str.len) return 0; - usz? start_slash = str.rindex_of_char('/'); - if (path_env != PathEnv.WIN32) return start_slash + 1 ?? 0; + sz? start_slash = str.rindex_of_char('/'); + if (path_env != PathEnv.WIN32) return (start_slash + 1) ?? 0; if (try index = str.rindex_of_char('\\')) { if (try start_slash && start_slash > index) return start_slash + 1; @@ -210,7 +207,7 @@ fn usz? start_of_base_name(String str, PathEnv path_env) @local if (str[0] != '\\') return index + 1; // Handle \\server\foo // Find the \ before "foo" - usz last_index = 2 + str[2..].index_of_char('\\')!; + sz last_index = 2 + str[2..].index_of_char('\\')!; // If they don't match, we're done if (last_index > index) return INVALID_PATH~; if (last_index != index) return index + 1; @@ -229,7 +226,7 @@ fn bool? Path.is_absolute(self) { String path_str = self.str_view(); if (!path_str.len) return false; - usz path_start = volume_name_len(path_str, self.env)!; + sz path_start = volume_name_len(path_str, self.env)!; if (path_start > 0 && path_str[0] == '\\') return true; return path_start < path_str.len && is_separator(path_str[path_start], self.env); } @@ -259,7 +256,7 @@ fn Path? Path.absolute(self, Allocator allocator) $if DEFAULT_ENV == WIN32: @pool() { - const usz BUFFER_LEN = 4096; + const sz BUFFER_LEN = 4096; WString buffer = (WString)mem::talloc_array(Char16, BUFFER_LEN); buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN); if (!buffer) return INVALID_PATH~; @@ -280,7 +277,7 @@ fn String? String.file_tbasename(self) => self.file_basename(tmem); fn String Path.basename(self) { - usz basename_start = start_of_base_name(self.path_string, self.env)!!; + sz basename_start = start_of_base_name(self.path_string, self.env)!!; String path_str = self.path_string; if (basename_start == path_str.len) return ""; return path_str[basename_start..]; @@ -296,9 +293,9 @@ fn String? String.path_dirname(self, Allocator allocator) => @pool() fn String Path.dirname(self) { String path_str = self.path_string; - usz basename_start = start_of_base_name(path_str, self.env)!!; + sz basename_start = start_of_base_name(path_str, self.env)!!; if (basename_start == 0) return "."; - usz start = volume_name_len(path_str, self.env)!!; + sz start = volume_name_len(path_str, self.env)!!; if (basename_start <= start + 1) { if (self.env == WIN32 && basename_start > start && path_str[0..1] == `\\`) @@ -330,7 +327,7 @@ fn bool Path.has_extension(self, String extension) fn String? Path.extension(self) { String basename = self.basename(); - usz index = basename.rindex_of(".")!; + sz index = basename.rindex_of(".")!; // Plain ".foo" does not have an extension if (index == 0 || index == basename.len) return ""; return basename[index + 1..]; @@ -338,7 +335,7 @@ fn String? Path.extension(self) fn String Path.volume_name(self) { - usz len = volume_name_len(self.str_view(), self.env)!!; + sz len = volume_name_len(self.str_view(), self.env)!!; if (!len) return ""; return self.path_string[:len]; } @@ -347,9 +344,9 @@ fn Path? String.to_path(self, Allocator allocator) => new(allocator, self); fn Path? String.to_tpath(self) => new(tmem, self); -fn usz? volume_name_len(String path, PathEnv path_env) @local +fn sz? volume_name_len(String path, PathEnv path_env) @local { - usz len = path.len; + sz len = path.len; if (len < 2 || path_env != PathEnv.WIN32) return 0; switch (path[0]) { @@ -361,8 +358,8 @@ fn usz? volume_name_len(String path, PathEnv path_env) @local // Not 2 => folded paths if (count != 2) return 0; // Check that we have a name followed by '\' - isz base_found = 0; - for (usz i = 2; i < len; i++) + sz base_found = 0; + for (sz i = 2; i < len; i++) { char c = path[i]; if (is_win32_separator(c)) @@ -419,15 +416,15 @@ fn Path? Path.parent(self) fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV) { if (!path_str.len) return path_str; - usz path_start = volume_name_len(path_str, path_env)!; + sz path_start = volume_name_len(path_str, path_env)!; if (path_start > 0 && path_env == PathEnv.WIN32) { - for (usz i = 0; i < path_start; i++) if (path_str[i] == '/') path_str[i] = '\\'; + for (sz i = 0; i < path_start; i++) if (path_str[i] == '/') path_str[i] = '\\'; } - usz path_len = path_str.len; + sz path_len = path_str.len; if (path_start == path_len) return path_str; char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX; - usz len = path_start; + sz len = path_start; bool has_root = is_separator(path_str[path_start], path_env); if (has_root) { @@ -438,7 +435,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV) // This allows us to avoid checking whether it is the start of the path. bool previous_was_separator = true; - for (usz i = path_start; i < path_len; i++) + for (sz i = path_start; i < path_len; i++) { char c = path_str[i]; // Fold foo///bar into foo/bar @@ -537,22 +534,20 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV) return path_str[:len]; } -fn ZString Path.as_zstr(self) @deprecated => (ZString)self.path_string.ptr; - fn String Path.root_directory(self) { String path_str = self.str_view(); - usz len = path_str.len; + sz len = path_str.len; if (!len) return ""; if (path_str == ".") return "."; if (self.env == PathEnv.WIN32) { - usz root_len = volume_name_len(path_str, self.env)!!; + sz root_len = volume_name_len(path_str, self.env)!!; if (root_len == len || !is_win32_separator(path_str[root_len])) return ""; return path_str[root_len..root_len]; } if (!is_posix_separator(path_str[0])) return ""; - for (usz i = 1; i < len; i++) + for (sz i = 1; i < len; i++) { if (is_posix_separator(path_str[i])) { @@ -625,9 +620,9 @@ fn bool Path.has_suffix(self, String str) => self.str_view().ends_with(str); <* @require self.allocator != null : "This Path should never be freed" *> -fn void Path.free(self) => allocator::free(self.allocator, self.path_string.ptr); +fn void Path.free(self) => alloc::free(self.allocator, self.path_string.ptr); -fn usz? Path.to_format(&self, Formatter* formatter) @dynamic => formatter.print(self.str_view()); +fn sz? Path.to_format(&self, Formatter* formatter) @dynamic => formatter.print(self.str_view()); const bool[256] RESERVED_PATH_CHAR_POSIX = { diff --git a/test/stdlib/src/io/stream.c3 b/test/stdlib/src/io/stream.c3 index aca181d..6293565 100644 --- a/test/stdlib/src/io/stream.c3 +++ b/test/stdlib/src/io/stream.c3 @@ -3,21 +3,20 @@ import std::math; -alias SetCursorFn = fn void?(void*, long offset, SeekOrigin whence = START); +alias SeekFn = fn void?(void*, long offset, SeekOrigin whence = FROM_START); interface InStream { fn void? close() @optional; fn long? cursor() @optional; - fn void? set_cursor(long offset, SeekOrigin whence = FROM_START) @optional; - fn usz? seek(isz offset, Seek seek) @optional; - fn usz len() @optional; - fn ulong? size() @optional; - fn ulong? available() @optional; - fn usz? read(char[] buffer); + fn void? seek(long offset, SeekOrigin whence = FROM_START) @optional; + fn long len() @optional; + fn long? size() @optional; + fn long? available() @optional; + fn sz? read(char[] buffer); fn char? read_byte(); - fn usz? write_to(OutStream out) @optional; + fn long? write_to(OutStream out) @optional; fn void? pushback_byte() @optional; } @@ -27,29 +26,22 @@ interface OutStream fn void? destroy() @optional; fn void? close() @optional; fn void? flush() @optional; - fn usz? write(char[] bytes); + fn sz? write(char[] bytes); fn void? write_byte(char c); - fn usz? read_to(InStream in) @optional; + fn long? read_to(InStream in) @optional; } -fn ulong? available(InStream s) +fn long? available(InStream s) { if (&s.available) return s.available(); - if (&s.set_cursor && &s.cursor) + if (&s.seek && &s.cursor) { long curr = s.cursor()!; - s.set_cursor(0, FROM_END)!; - ulong len = s.cursor()!; - s.set_cursor(curr)!; + s.seek(0, FROM_END)!; + long len = s.cursor()!; + s.seek(curr)!; return len - curr; } - if (&s.seek) - { - usz curr = s.seek(0, Seek.CURSOR)!; - usz len = s.seek(0, Seek.END)!; - s.seek(curr, Seek.SET)!; - return (ulong)len - (ulong)curr; - } return io::UNSUPPORTED_OPERATION~; } @@ -77,28 +69,28 @@ macro bool @is_not_outstream_if_ptr(#expr) @const @param [&out] ref @require @is_instream(stream) : "Expected a stream" *> -macro usz? read_any(stream, any ref) +macro sz? read_any(stream, any ref) { - return read_all(stream, ((char*)ref)[:ref.type.sizeof]); + return read_all(stream, ((char*)ref)[:ref.type.size]); } <* @param [&in] ref : "the object to write." @require @is_outstream(stream) - @ensure return == ref.type.sizeof + @ensure return == ref.type.size *> -macro usz? write_any(stream, any ref) +macro sz? write_any(stream, any ref) { - return write_all(stream, ((char*)ref)[:ref.type.sizeof]); + return write_all(stream, ((char*)ref)[:ref.type.size]); } <* @require @is_instream(stream) *> -macro usz? read_all(stream, char[] buffer) +macro sz? read_all(stream, char[] buffer) { if (buffer.len == 0) return 0; - usz n = stream.read(buffer)!; + sz n = stream.read(buffer)!; if (n != buffer.len) return UNEXPECTED_EOF~; return n; } @@ -113,9 +105,10 @@ macro char[]? read_fully(Allocator allocator, stream) // Efficient path if it is possible to pre-allocate if (try len = available(stream)) { - char* data = allocator::malloc_try(allocator, len)!; - defer catch allocator::free(allocator, data); - usz read = 0; + if (len > sz::max) return mem::INVALID_ALLOC_SIZE~; + char* data = alloc::malloc_try(allocator, (sz)len)!; + defer catch alloc::free(allocator, data); + sz read = 0; while (read < len) { read += stream.read(data[read:len - read])!; @@ -132,10 +125,10 @@ macro char[]? read_fully(Allocator allocator, stream) <* @require @is_outstream(stream) *> -macro usz? write_all(stream, char[] buffer) +macro sz? write_all(stream, char[] buffer) { if (buffer.len == 0) return 0; - usz n = stream.write(buffer)!; + sz n = stream.write(buffer)!; if (n != buffer.len) return INCOMPLETE_WRITE~; return n; } @@ -143,9 +136,9 @@ macro usz? write_all(stream, char[] buffer) <* @require @is_instream(s) *> -macro usz? read_using_read_byte(s, char[] buffer) +macro sz? read_using_read_byte(s, char[] buffer) { - usz len = 0; + sz len = 0; foreach (&cptr : buffer) { char? c = s.read_byte(); @@ -175,7 +168,7 @@ macro void? write_byte_using_write(s, char c) macro char? read_byte_using_read(s) { char[1] buffer; - usz read = s.read(&buffer)!; + sz read = s.read(&buffer)!; if (read != 1) return io::EOF~; return buffer[0]; } @@ -185,7 +178,7 @@ alias ReadByteFn = fn char?(); <* @require @is_outstream(s) *> -macro usz? write_using_write_byte(s, char[] bytes) +macro sz? write_using_write_byte(s, char[] bytes) { foreach (c : bytes) s.write_byte(c)!; return bytes.len; @@ -193,15 +186,10 @@ macro usz? write_using_write_byte(s, char[] bytes) macro void? pushback_using_seek(s) { - if (&s.set_cursor) - { - s.set_cursor(-1, FROM_CURSOR)!; - return; - } - s.seek(-1, CURSOR)!; + s.seek(-1, FROM_CURSOR)!; } -fn usz? copy_to(InStream in, OutStream dst, char[] buffer = {}) +fn long? copy_to(InStream in, OutStream dst, char[] buffer = {}) { if (buffer.len) return copy_through_buffer(in, dst, buffer); if (&in.write_to) return in.write_to(dst); @@ -217,19 +205,19 @@ fn usz? copy_to(InStream in, OutStream dst, char[] buffer = {}) $endswitch } -macro usz? copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local +macro long? copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local { - usz total_copied; + long total_copied; while (true) { - usz? len = in.read(buffer); + sz? len = in.read(buffer); if (catch err = len) { if (err == io::EOF) return total_copied; return err~; } if (!len) return total_copied; - usz written = dst.write(buffer[:len])!; + sz written = dst.write(buffer[:len])!; total_copied += len; if (written != len) return INCOMPLETE_WRITE~; } @@ -239,20 +227,20 @@ const char[*] MAX_VARS @private = { [1] = 2, [2] = 3, [4] = 5, [8] = 10, [16] = <* @require @is_instream(stream) - @require $kindof(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int() + @require @kindof(x_ptr) == POINTER && @kindof(*x_ptr).is_int() *> -macro usz? read_varint(stream, x_ptr) +macro sz? read_varint(stream, x_ptr) { - return read_varint_generic{$typeof(stream), $typeof(x_ptr).inner}(stream, x_ptr); + return read_varint_generic{$typeof(stream), $typeof(x_ptr)::inner}(stream, x_ptr); } -fn usz? read_varint_generic(VarInStream stream, Type* ptr) @private +fn sz? read_varint_generic(VarInStream stream, Type* ptr) @private { - const MAX = MAX_VARS[Type.sizeof]; + const MAX = MAX_VARS[Type::size]; Type x; uint shift; - usz n; - for (usz i = 0; i < MAX; i++) + sz n; + for (sz i = 0; i < MAX; i++) { char? c = stream.read_byte(); if (catch err = c) @@ -265,7 +253,7 @@ fn usz? read_varint_generic(VarInStream stream, Type* ptr) @ { if (i + 1 == MAX && c > 1) break; x |= (Type)c << shift; - $if Type.kindof == SIGNED_INT: + $if Type::kind == SIGNED_INT: x = x & 1 == 0 ? x >> 1 : ~(x >> 1); $endif *ptr = x; @@ -280,18 +268,18 @@ fn usz? read_varint_generic(VarInStream stream, Type* ptr) @ <* @require @is_outstream(stream) - @require $kindof(x).is_int() + @require @kindof(x).is_int() *> -macro usz? write_varint(stream, x) +macro sz? write_varint(stream, x) { return write_varint_generic{$typeof(stream), $typeof(x)}(stream, x); } -fn usz? write_varint_generic(VarOutStream stream, Type x) @private +fn sz? write_varint_generic(VarOutStream stream, Type x) @private { - const MAX = MAX_VARS[Type.sizeof]; + const MAX = MAX_VARS[Type::size]; char[MAX] buffer @noinit; - usz i; + sz i; while (x >= 0x80) { buffer[i] = (char)(x | 0x80); @@ -302,6 +290,43 @@ fn usz? write_varint_generic(VarOutStream stream, Type x) @ return write_all(stream, buffer[:i + 1]); } +<* @require @is_instream(stream) *> +macro short? read_be_short(stream) => (short)read_be_ushort(stream); +<* @require @is_instream(stream) *> +macro int? read_be_int(stream) => (int)read_be_uint(stream); +<* @require @is_instream(stream) *> +macro long? read_be_long(stream) => (long)read_be_ulong(stream); +<* @require @is_instream(stream) *> +macro int128? read_be_int128(stream) => (int128)read_be_uint128(stream); + +<* @require @is_instream(stream) *> +macro short? read_le_short(stream) => (short)read_le_ushort(stream); +<* @require @is_instream(stream) *> +macro int? read_le_int(stream) => (int)read_le_uint(stream); +<* @require @is_instream(stream) *> +macro long? read_le_long(stream) => (long)read_le_ulong(stream); +<* @require @is_instream(stream) *> +macro int128? read_le_int128(stream) => (int128)read_le_uint128(stream); + + +<* @require @is_outstream(stream) *> +macro void? write_be_short(stream, short s) => write_be_ushort(stream, (ushort)s); +<* @require @is_outstream(stream) *> +macro void? write_be_int(stream, int s) => write_be_uint(stream, (uint)s); +<* @require @is_outstream(stream) *> +macro void? write_be_long(stream, long s) => write_be_ulong(stream, (ulong)s); +<* @require @is_outstream(stream) *> +macro void? write_be_int128(stream, int128 s) => write_be_uint128(stream, (uint128)s); + +<* @require @is_outstream(stream) *> +macro void? write_le_short(stream, short s) => write_le_ushort(stream, (ushort)s); +<* @require @is_outstream(stream) *> +macro void? write_le_int(stream, int s) => write_le_uint(stream, (uint)s); +<* @require @is_outstream(stream) *> +macro void? write_le_long(stream, long s) => write_le_ulong(stream, (ulong)s); +<* @require @is_outstream(stream) *> +macro void? write_le_int128(stream, int128 s) => write_le_uint128(stream, (uint128)s); + <* @require @is_instream(stream) *> @@ -322,26 +347,10 @@ macro ushort? read_le_ushort(stream) return (ushort)(hi_byte << 8 | lo_byte); } -<* - @require @is_instream(stream) -*> -macro short? read_be_short(stream) -{ - return read_be_ushort(stream); -} - -<* - @require @is_instream(stream) -*> -macro short? read_le_short(stream) -{ - return read_le_ushort(stream); -} - <* @require @is_outstream(stream) *> -macro void? write_be_short(stream, ushort s) +macro void? write_be_ushort(stream, ushort s) { stream.write_byte((char)(s >> 8))!; stream.write_byte((char)s)!; @@ -350,7 +359,7 @@ macro void? write_be_short(stream, ushort s) <* @require @is_outstream(stream) *> -macro void? write_le_short(stream, ushort s) +macro void? write_le_ushort(stream, ushort s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; @@ -378,26 +387,11 @@ macro uint? read_le_uint(stream) return val + stream.read_byte()! << 24; } -<* - @require @is_instream(stream) -*> -macro int? read_be_int(stream) -{ - return read_be_uint(stream); -} - -<* - @require @is_instream(stream) -*> -macro int? read_le_int(stream) -{ - return read_le_uint(stream); -} <* @require @is_outstream(stream) *> -macro void? write_be_int(stream, uint s) +macro void? write_be_uint(stream, uint s) { stream.write_byte((char)(s >> 24))!; stream.write_byte((char)(s >> 16))!; @@ -408,7 +402,7 @@ macro void? write_be_int(stream, uint s) <* @require @is_outstream(stream) *> -macro void? write_le_int(stream, uint s) +macro void? write_le_uint(stream, uint s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; @@ -446,26 +440,10 @@ macro ulong? read_le_ulong(stream) return val + (ulong)stream.read_byte()! << 56; } -<* - @require @is_instream(stream) -*> -macro long? read_be_long(stream) -{ - return read_be_ulong(stream); -} - -<* - @require @is_instream(stream) -*> -macro long? read_le_long(stream) -{ - return read_le_ulong(stream); -} - <* @require @is_outstream(stream) *> -macro void? write_be_long(stream, ulong s) +macro void? write_be_ulong(stream, ulong s) { stream.write_byte((char)(s >> 56))!; stream.write_byte((char)(s >> 48))!; @@ -480,7 +458,7 @@ macro void? write_be_long(stream, ulong s) <* @require @is_outstream(stream) *> -macro void? write_le_long(stream, ulong s) +macro void? write_le_ulong(stream, ulong s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; @@ -538,26 +516,10 @@ macro uint128? read_le_uint128(stream) return val + (uint128)stream.read_byte()! << 120; } -<* - @require @is_instream(stream) -*> -macro int128? read_be_int128(stream) -{ - return read_be_uint128(stream); -} - -<* - @require @is_instream(stream) -*> -macro int128? read_le_int128(stream) -{ - return read_le_uint128(stream); -} - <* @require @is_outstream(stream) *> -macro void? write_be_int128(stream, uint128 s) +macro void? write_be_uint128(stream, uint128 s) { stream.write_byte((char)(s >> 120))!; stream.write_byte((char)(s >> 112))!; @@ -580,7 +542,7 @@ macro void? write_be_int128(stream, uint128 s) <* @require @is_outstream(stream) *> -macro void? write_le_int128(stream, uint128 s) +macro void? write_le_uint128(stream, uint128 s) { stream.write_byte((char)s)!; stream.write_byte((char)(s >> 8))!; @@ -605,7 +567,7 @@ macro void? write_le_int128(stream, uint128 s) @require @is_outstream(stream) @require data.len < 256 : "Data exceeded 255" *> -macro usz? write_tiny_bytearray(stream, char[] data) +macro sz? write_tiny_bytearray(stream, char[] data) { stream.write_byte((char)data.len)!; return stream.write(data) + 1; @@ -618,7 +580,7 @@ macro char[]? read_tiny_bytearray(stream, Allocator allocator) { int len = stream.read_byte()!; if (!len) return {}; - char[] data = allocator::alloc_array(allocator, char, len); + char[] data = alloc::alloc_array(allocator, char, len); io::read_all(stream, data)!; return data; } @@ -627,9 +589,9 @@ macro char[]? read_tiny_bytearray(stream, Allocator allocator) @require @is_outstream(stream) @require data.len < 0x1000 : "Data exceeded 65535" *> -macro usz? write_short_bytearray(stream, char[] data) +macro sz? write_short_bytearray(stream, char[] data) { - io::write_be_short(stream, (ushort)data.len)!; + io::write_be_ushort(stream, (ushort)data.len)!; return stream.write(data) + 2; } @@ -640,7 +602,7 @@ macro char[]? read_short_bytearray(stream, Allocator allocator) { int len = io::read_be_ushort(stream)!; if (!len) return {}; - char[] data = allocator::alloc_array(allocator, char, len); + char[] data = alloc::alloc_array(allocator, char, len); io::read_all(stream, data)!; return data; } @@ -648,31 +610,24 @@ macro char[]? read_short_bytearray(stream, Allocator allocator) <* @require @is_instream(stream) *> -macro void? skip(stream, usz bytes) +macro void? skip(stream, sz bytes) { if (!bytes) return; $switch: $case $typeof(stream) == InStream: - if (!&stream.seek && !&stream.set_cursor) + if (!&stream.seek) { - for (usz i = 0; i < bytes; i++) + for (sz i = 0; i < bytes; i++) { stream.read()!; } return; } - if (!&stream.set_cursor) - { - stream.seek(bytes, CURSOR)!; - return; - } - stream.set_cursor(bytes, FROM_CURSOR)!; - $case $defined(stream.set_cursor): - stream.set_cursor(bytes, FROM_CURSOR)!; + stream.seek(bytes, FROM_CURSOR)!; $case $defined(stream.seek): - stream.seek(bytes, CURSOR)!; + stream.seek(bytes, FROM_CURSOR)!; $default: - for (usz i = 0; i < bytes; i++) + for (sz i = 0; i < bytes; i++) { stream.read()!; } @@ -685,4 +640,4 @@ macro void? skip(stream, usz bytes) fn ByteReader wrap_bytes(char[] bytes) { return { bytes, 0 }; -} \ No newline at end of file +} diff --git a/test/stdlib/src/io/stream/buffer.c3 b/test/stdlib/src/io/stream/buffer.c3 index 792fb1c..386ba2e 100644 --- a/test/stdlib/src/io/stream/buffer.c3 +++ b/test/stdlib/src/io/stream/buffer.c3 @@ -4,8 +4,8 @@ struct ReadBuffer (InStream) { InStream wrapped_stream; char[] bytes; - usz read_idx; - usz write_idx; + sz read_idx; + sz write_idx; } <* @@ -29,7 +29,7 @@ fn void? ReadBuffer.close(&self) @dynamic if (&self.wrapped_stream.close) self.wrapped_stream.close()!; } -fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic +fn sz? ReadBuffer.read(&self, char[] bytes) @dynamic { if (self.read_idx == self.write_idx) { @@ -40,7 +40,7 @@ fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic } readbuffer_refill(self)!; } - usz n = min(self.write_idx - self.read_idx, bytes.len); + sz n = min(self.write_idx - self.read_idx, bytes.len); bytes[:n] = self.bytes[self.read_idx:n]; self.read_idx += n; return n; @@ -65,7 +65,7 @@ struct WriteBuffer (OutStream) { OutStream wrapped_stream; char[] bytes; - usz index; + sz index; } <* @@ -96,9 +96,9 @@ fn void? WriteBuffer.flush(&self) @dynamic if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!; } -fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic +fn sz? WriteBuffer.write(&self, char[] bytes) @dynamic { - usz n = self.bytes.len - self.index; + sz n = self.bytes.len - self.index; if (bytes.len < n) { // Enough room in the buffer. @@ -120,7 +120,7 @@ fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic fn void? WriteBuffer.write_byte(&self, char c) @dynamic { - usz n = self.bytes.len - self.index; + sz n = self.bytes.len - self.index; if (n == 0) { write_buffer_write_pending(self)!; diff --git a/test/stdlib/src/io/stream/bytebuffer.c3 b/test/stdlib/src/io/stream/bytebuffer.c3 index 4303478..bf89858 100644 --- a/test/stdlib/src/io/stream/bytebuffer.c3 +++ b/test/stdlib/src/io/stream/bytebuffer.c3 @@ -4,10 +4,10 @@ import std::math; struct ByteBuffer (InStream, OutStream) { Allocator allocator; - usz max_read; + sz max_read; char[] bytes; - usz read_idx; - usz write_idx; + sz read_idx; + sz write_idx; bool has_last; } @@ -16,7 +16,7 @@ struct ByteBuffer (InStream, OutStream) max_read defines how many bytes might be kept before its internal buffer is shrunk. @require self.bytes.len == 0 : "Buffer already initialized." *> -fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16) +fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, sz max_read, sz initial_capacity = 16) { *self = { .allocator = allocator, .max_read = max_read }; initial_capacity = max(initial_capacity, 16); @@ -24,7 +24,7 @@ fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz ini return self; } -fn ByteBuffer* ByteBuffer.tinit(&self, usz max_read, usz initial_capacity = 16) +fn ByteBuffer* ByteBuffer.tinit(&self, sz max_read, sz initial_capacity = 16) { return self.init(tmem, max_read, initial_capacity); } @@ -41,13 +41,13 @@ fn ByteBuffer* ByteBuffer.init_with_buffer(&self, char[] buf) fn void ByteBuffer.free(&self) { - if (self.allocator) allocator::free(self.allocator, self.bytes); + if (self.allocator) alloc::free(self.allocator, self.bytes); *self = {}; } -fn usz? ByteBuffer.write(&self, char[] bytes) @dynamic +fn sz? ByteBuffer.write(&self, char[] bytes) @dynamic { - usz cap = self.bytes.len - self.write_idx; + sz cap = self.bytes.len - self.write_idx; if (cap < bytes.len) self.grow(bytes.len); self.bytes[self.write_idx:bytes.len] = bytes[..]; self.write_idx += bytes.len; @@ -56,21 +56,21 @@ fn usz? ByteBuffer.write(&self, char[] bytes) @dynamic fn void? ByteBuffer.write_byte(&self, char c) @dynamic { - usz cap = self.bytes.len - self.write_idx; + sz cap = self.bytes.len - self.write_idx; if (cap == 0) self.grow(1); self.bytes[self.write_idx] = c; self.write_idx++; } -fn usz? ByteBuffer.read(&self, char[] bytes) @dynamic +fn sz? ByteBuffer.read(&self, char[] bytes) @dynamic { - usz readable = self.write_idx - self.read_idx; + sz readable = self.write_idx - self.read_idx; if (readable == 0) { self.has_last = false; return io::EOF~; } - usz n = min(readable, bytes.len); + sz n = min(readable, bytes.len); bytes[:n] = self.bytes[self.read_idx:n]; self.read_idx += n; self.has_last = n > 0; @@ -80,7 +80,7 @@ fn usz? ByteBuffer.read(&self, char[] bytes) @dynamic fn char? ByteBuffer.read_byte(&self) @dynamic { - usz readable = self.write_idx - self.read_idx; + sz readable = self.write_idx - self.read_idx; if (readable == 0) { self.has_last = false; @@ -109,38 +109,32 @@ fn long? ByteBuffer.cursor(&self) @dynamic return self.read_idx; } -fn void? ByteBuffer.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic +fn void? ByteBuffer.seek(&self, long offset, SeekOrigin whence = FROM_START) @dynamic { switch (whence) { case FROM_START: if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~; - self.read_idx = (usz)offset; + self.read_idx = (sz)offset; case FROM_CURSOR: if ((offset < 0 && self.read_idx < -offset) || (offset > 0 && self.read_idx + offset > self.write_idx)) return INVALID_POSITION~; - self.read_idx += (usz)offset; + self.read_idx += (sz)offset; case FROM_END: if (offset < 0 || offset > self.write_idx) return INVALID_POSITION~; - self.read_idx = self.write_idx - (usz)offset; + self.read_idx = self.write_idx - (sz)offset; } } -fn usz? ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic +fn long? ByteBuffer.available(&self) @inline @dynamic { - self.set_cursor(offset, (SeekOrigin)seek.ordinal)!; - return (usz)self.cursor(); + return (long)self.write_idx - self.read_idx; } -fn ulong? ByteBuffer.available(&self) @inline @dynamic -{ - return (ulong)self.write_idx - self.read_idx; -} - -fn void ByteBuffer.grow(&self, usz n) +fn void ByteBuffer.grow(&self, sz n) { n = math::next_power_of_2(self.bytes.len + n); - char* p = allocator::realloc(self.allocator, self.bytes, n); + char* p = alloc::realloc(self.allocator, self.bytes, n); self.bytes = p[:n]; } @@ -149,7 +143,7 @@ macro void ByteBuffer.shrink(&self) if (self.read_idx >= self.max_read) { // Drop the read data besides the last byte (for pushback_byte). - usz readable = self.write_idx - self.read_idx; + sz readable = self.write_idx - self.read_idx; self.bytes[:1 + readable] = self.bytes[self.read_idx - 1:1 + readable]; self.write_idx = 1 + readable; self.read_idx = 1; diff --git a/test/stdlib/src/io/stream/bytereader.c3 b/test/stdlib/src/io/stream/bytereader.c3 index 32a6496..f6262ab 100644 --- a/test/stdlib/src/io/stream/bytereader.c3 +++ b/test/stdlib/src/io/stream/bytereader.c3 @@ -3,10 +3,10 @@ module std::io; struct ByteReader (InStream) { char[] bytes; - usz index; + sz index; } -fn usz ByteReader.len(&self) @dynamic +fn long ByteReader.len(&self) @dynamic { return self.bytes.len; } @@ -17,10 +17,10 @@ fn ByteReader* ByteReader.init(&self, char[] bytes) return self; } -fn usz? ByteReader.read(&self, char[] bytes) @dynamic +fn sz? ByteReader.read(&self, char[] bytes) @dynamic { if (self.index >= self.bytes.len) return io::EOF~; - usz len = min(self.bytes.len - self.index, bytes.len); + sz len = min(self.bytes.len - self.index, bytes.len); if (len == 0) return 0; mem::copy(bytes.ptr, &self.bytes[self.index], len); self.index += len; @@ -39,40 +39,34 @@ fn void? ByteReader.pushback_byte(&self) @dynamic self.index--; } -fn usz? ByteReader.seek(&self, isz offset, Seek seek) @dynamic -{ - self.set_cursor((long)offset, (SeekOrigin)seek.ordinal)!; - return (usz)self.cursor(); -} - fn long? ByteReader.cursor(&self) @dynamic { return self.index; } -fn void? ByteReader.set_cursor(&self, long offset, SeekOrigin whence = FROM_START) @dynamic +fn void? ByteReader.seek(&self, long offset, SeekOrigin whence = FROM_START) @dynamic { long new_index; switch (whence) { - case FROM_START: new_index = offset; + case FROM_START: new_index = offset; case FROM_CURSOR: new_index = self.index + offset; - case FROM_END: new_index = self.bytes.len + offset; + case FROM_END: new_index = self.bytes.len + offset; } if (new_index < 0 || new_index > self.bytes.len) return INVALID_POSITION~; - self.index = (usz)new_index; + self.index = (sz)new_index; } -fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic +fn long? ByteReader.write_to(&self, OutStream writer) @dynamic { if (self.index >= self.bytes.len) return 0; - usz written = writer.write(self.bytes[self.index..])!; + sz written = writer.write(self.bytes[self.index..])!; self.index += written; assert(self.index <= self.bytes.len); return written; } -fn ulong? ByteReader.available(&self) @inline @dynamic +fn long? ByteReader.available(&self) @inline @dynamic { return max(0, self.bytes.len - self.index); } diff --git a/test/stdlib/src/io/stream/bytewriter.c3 b/test/stdlib/src/io/stream/bytewriter.c3 index ffebc71..14d40f5 100644 --- a/test/stdlib/src/io/stream/bytewriter.c3 +++ b/test/stdlib/src/io/stream/bytewriter.c3 @@ -4,30 +4,35 @@ import std::math; struct ByteWriter (OutStream) { char[] bytes; - usz index; + sz index; Allocator allocator; } <* @param [&inout] self @param [&inout] allocator + @param reserve : "The bytes to reserve up front, defaults to 0" + @require self.bytes.len == 0 : "Init may not run on already initialized data" @ensure (bool)allocator, self.index == 0 *> -fn ByteWriter* ByteWriter.init(&self, Allocator allocator) +fn ByteWriter* ByteWriter.init(&self, Allocator allocator, sz reserve = 0) { *self = { .bytes = {}, .allocator = allocator }; + if (reserve > 0) self.ensure_capacity(reserve)!!; return self; } <* @param [&inout] self + @param reserve : "The bytes to reserve up front, defaults to 0" + @require self.bytes.len == 0 : "Init may not run on already initialized data" @ensure self.index == 0 *> -fn ByteWriter* ByteWriter.tinit(&self) +fn ByteWriter* ByteWriter.tinit(&self, sz reserve = 0) { - return self.init(tmem) @inline; + return self.init(tmem, reserve) @inline; } fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data) @@ -39,7 +44,7 @@ fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data) fn void? ByteWriter.destroy(&self) @dynamic { if (!self.allocator) return; - if (void* ptr = self.bytes.ptr) allocator::free(self.allocator, ptr); + if (void* ptr = self.bytes.ptr) alloc::free(self.allocator, ptr); *self = { }; } @@ -53,17 +58,17 @@ fn String ByteWriter.str_view(&self) @inline return (String)self.bytes[:self.index]; } -fn void? ByteWriter.ensure_capacity(&self, usz len) @inline +fn void? ByteWriter.ensure_capacity(&self, sz len) @inline { if (self.bytes.len > len) return; if (!self.allocator) return OUT_OF_SPACE~; if (len < 16) len = 16; - usz new_capacity = math::next_power_of_2(len); - char* new_ptr = allocator::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!; + sz new_capacity = math::next_power_of_2(len); + char* new_ptr = alloc::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!; self.bytes = new_ptr[:new_capacity]; } -fn usz? ByteWriter.write(&self, char[] bytes) @dynamic +fn sz? ByteWriter.write(&self, char[] bytes) @dynamic { self.ensure_capacity(self.index + bytes.len)!; mem::copy(&self.bytes[self.index], bytes.ptr, bytes.len); @@ -81,19 +86,19 @@ fn void? ByteWriter.write_byte(&self, char c) @dynamic @param [&inout] self @param reader *> -fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic +fn long? ByteWriter.read_from(&self, InStream reader) @dynamic { - usz start_index = self.index; + sz start_index = self.index; if (&reader.available) { - while (ulong available = reader.available()!) + while (long available = reader.available()!) { - if (available > usz.max) return OUT_OF_SPACE~; - self.ensure_capacity(self.index + (usz)available)!; - usz read = reader.read(self.bytes[self.index..])!; + if (available > sz::max) return OUT_OF_SPACE~; + self.ensure_capacity(self.index + (sz)available)!; + sz read = reader.read(self.bytes[self.index..])!; self.index += read; } - return self.index - start_index; + return (long)self.index - start_index; } if (self.bytes.len == 0) { @@ -102,7 +107,7 @@ fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic while (true) { // See how much we can read. - usz len_to_read = self.bytes.len - self.index; + sz len_to_read = self.bytes.len - self.index; // Less than 16 bytes? Double the capacity if (len_to_read < 16) { @@ -110,10 +115,10 @@ fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic len_to_read = self.bytes.len - self.index; } // Read into the rest of the buffer - usz read = reader.read(self.bytes[self.index..])!; + sz read = reader.read(self.bytes[self.index..])!; self.index += read; // Ok, we reached the end. - if (read < len_to_read) return self.index - start_index; + if (read < len_to_read) return (long)self.index - start_index; // Otherwise go another round } } diff --git a/test/stdlib/src/io/stream/limitreader.c3 b/test/stdlib/src/io/stream/limitreader.c3 index b5fe229..d735c47 100644 --- a/test/stdlib/src/io/stream/limitreader.c3 +++ b/test/stdlib/src/io/stream/limitreader.c3 @@ -3,14 +3,14 @@ module std::io; struct LimitReader (InStream) { InStream wrapped_stream; - usz limit; + long limit; } <* @param [&inout] wrapped_stream : "The stream to read from" @param limit : "The max limit to read" *> -fn LimitReader* LimitReader.init(&self, InStream wrapped_stream, usz limit) +fn LimitReader* LimitReader.init(&self, InStream wrapped_stream, long limit) { *self = { .wrapped_stream = wrapped_stream, .limit = limit }; return self; @@ -22,13 +22,13 @@ fn void? LimitReader.close(&self) @dynamic } -fn usz? LimitReader.read(&self, char[] bytes) @dynamic +fn sz? LimitReader.read(&self, char[] bytes) @dynamic { if (self.limit == 0) return io::EOF~; - usz m = min(bytes.len, self.limit); - usz n = self.wrapped_stream.read(bytes[:m])!; + long m = min(bytes.len, self.limit); + long n = self.wrapped_stream.read(bytes[:m])!; self.limit -= n; - return n; + return (sz)n; } fn char? LimitReader.read_byte(&self) @dynamic @@ -38,7 +38,7 @@ fn char? LimitReader.read_byte(&self) @dynamic return self.wrapped_stream.read_byte(); } -fn ulong? LimitReader.available(&self) @inline @dynamic +fn long? LimitReader.available(&self) @inline @dynamic { return self.limit; } \ No newline at end of file diff --git a/test/stdlib/src/io/stream/multireader.c3 b/test/stdlib/src/io/stream/multireader.c3 index f419a0c..6de4eb1 100644 --- a/test/stdlib/src/io/stream/multireader.c3 +++ b/test/stdlib/src/io/stream/multireader.c3 @@ -7,7 +7,7 @@ module std::io; struct MultiReader (InStream) { InStream[] readers; - usz index; + sz index; Allocator allocator; } @@ -20,7 +20,7 @@ struct MultiReader (InStream) *> fn MultiReader* MultiReader.init(&self, Allocator allocator, InStream... readers) { - InStream []copy = allocator::new_array(allocator, InStream, readers.len); + InStream []copy = alloc::new_array(allocator, InStream, readers.len); copy[..] = readers[..]; *self = { .readers = copy, .allocator = allocator }; return self; @@ -39,14 +39,14 @@ fn MultiReader* MultiReader.tinit(&self, InStream... readers) fn void MultiReader.free(&self) { if (!self.allocator) return; - allocator::free(self.allocator, self.readers); + alloc::free(self.allocator, self.readers); *self = {}; } -fn usz? MultiReader.read(&self, char[] bytes) @dynamic +fn sz? MultiReader.read(&self, char[] bytes) @dynamic { InStream r = self.readers[self.index]; - usz? n = r.read(bytes); + sz? n = r.read(bytes); if (catch err = n) { if (err != io::EOF) return err~; diff --git a/test/stdlib/src/io/stream/multiwriter.c3 b/test/stdlib/src/io/stream/multiwriter.c3 index 8dbc6e3..d59b192 100644 --- a/test/stdlib/src/io/stream/multiwriter.c3 +++ b/test/stdlib/src/io/stream/multiwriter.c3 @@ -17,7 +17,7 @@ struct MultiWriter (OutStream) *> fn MultiWriter* MultiWriter.init(&self, Allocator allocator, OutStream... writers) { - OutStream[] copy = allocator::new_array(allocator, OutStream, writers.len); + OutStream[] copy = alloc::new_array(allocator, OutStream, writers.len); copy[..] = writers[..]; *self = { .writers = copy, .allocator = allocator }; return self; @@ -36,13 +36,13 @@ fn MultiWriter* MultiWriter.tinit(&self, OutStream... writers) fn void MultiWriter.free(&self) { if (!self.allocator) return; - allocator::free(self.allocator, self.writers); + alloc::free(self.allocator, self.writers); *self = {}; } -fn usz? MultiWriter.write(&self, char[] bytes) @dynamic +fn sz? MultiWriter.write(&self, char[] bytes) @dynamic { - usz n; + sz n; foreach (w : self.writers) { n = w.write(bytes)!; diff --git a/test/stdlib/src/io/stream/scanner.c3 b/test/stdlib/src/io/stream/scanner.c3 index 76e875a..7de5ff0 100644 --- a/test/stdlib/src/io/stream/scanner.c3 +++ b/test/stdlib/src/io/stream/scanner.c3 @@ -4,8 +4,8 @@ struct Scanner (InStream) { InStream wrapped_stream; char[] buf; - usz pattern_idx; - usz read_idx; + sz pattern_idx; + sz read_idx; } <* @@ -28,7 +28,7 @@ fn void Scanner.init(&self, InStream stream, char[] buffer) fn char[] Scanner.flush(&self) @dynamic { assert(self.read_idx >= self.pattern_idx); - usz n = self.read_idx - self.pattern_idx; + sz n = self.read_idx - self.pattern_idx; char[] buf = self.buf[self.pattern_idx:n]; self.pattern_idx = 0; self.read_idx = 0; @@ -54,7 +54,7 @@ fn char[]? Scanner.scan(&self, String pattern = "\n") self.pattern_idx = 0; } assert(self.read_idx >= self.pattern_idx); - usz n = self.read_idx - self.pattern_idx; + sz n = self.read_idx - self.pattern_idx; char[] buf = self.buf[self.pattern_idx:n]; if (try i = self.find(buf, pattern)) { @@ -72,24 +72,24 @@ fn char[]? Scanner.scan(&self, String pattern = "\n") self.pattern_idx = 0; buf = self.buf[n..]; - usz p = self.refill(buf)!; + sz p = self.refill(buf)!; self.read_idx = n + p; buf = buf[:p]; - usz i = self.find(buf, pattern)!; + sz i = self.find(buf, pattern)!; self.pattern_idx = n + i + pattern.len; return self.buf[:n + i]; } -macro usz? Scanner.find(&self, buf, pattern) @private +macro sz? Scanner.find(&self, buf, pattern) @private { return ((String)buf).index_of(pattern); } -macro usz? Scanner.refill(&self, buf) @private +macro sz? Scanner.refill(&self, buf) @private { - usz? n = self.wrapped_stream.read(buf); + sz? n = self.wrapped_stream.read(buf); if (catch err = n) { if (err == io::EOF) return NOT_FOUND~; @@ -98,9 +98,9 @@ macro usz? Scanner.refill(&self, buf) @private return n; } -fn usz? Scanner.read(&self, char[] bytes) @dynamic +fn sz? Scanner.read(&self, char[] bytes) @dynamic { - usz n; + sz n; if (self.pattern_idx < self.read_idx) { n = min(bytes.len, self.read_idx - self.pattern_idx); diff --git a/test/stdlib/src/io/stream/teereader.c3 b/test/stdlib/src/io/stream/teereader.c3 index ca0a075..1fbe5d4 100644 --- a/test/stdlib/src/io/stream/teereader.c3 +++ b/test/stdlib/src/io/stream/teereader.c3 @@ -25,9 +25,9 @@ fn TeeReader* TeeReader.init(&self, InStream r, OutStream w) return self; } -fn usz? TeeReader.read(&self, char[] bytes) @dynamic +fn sz? TeeReader.read(&self, char[] bytes) @dynamic { - usz nr, nw; + sz nr, nw; nr = self.r.read(bytes)!; nw = self.w.write(bytes[:nr])!; if (nr != nw) return GENERAL_ERROR~; diff --git a/test/stdlib/src/libc/libc.c3 b/test/stdlib/src/libc/libc.c3 index 3c0f419..e607c1f 100644 --- a/test/stdlib/src/libc/libc.c3 +++ b/test/stdlib/src/libc/libc.c3 @@ -49,7 +49,6 @@ fn void errno_set(Errno e) os::errno_set((int)e); } -typedef Errno @constinit = inline CInt; alias TerminateFunction = fn void(); alias CompareFunction = fn int(void*, void*); alias JmpBuf = uptr[$$JMP_BUF_SIZE]; @@ -64,8 +63,8 @@ const CInt SIGFPE = 8; const CInt SIGSEGV = 11; const CInt SIGTERM = 15; -alias Time_t = $typefrom(env::WIN32 ? long.typeid : CLong.typeid); -alias Off_t = $typefrom(env::WIN32 ? int.typeid : isz.typeid); +alias Time_t = $typefrom(env::WIN32 ? long::typeid : CLong::typeid); +alias Off_t = $typefrom(env::WIN32 ? int::typeid : sz::typeid); module libc @if(env::LIBC); @@ -136,8 +135,8 @@ extern fn CInt puts(ZString str); extern fn void qsort(void* base, usz items, usz size, CompareFunction compare); extern fn CInt raise(CInt signal); extern fn CInt rand(); -extern fn isz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32); -extern fn isz readlink(ZString pathname, char* buf, int bufsize) @if(!env::WIN32); +extern fn sz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32); +extern fn sz readlink(ZString pathname, char* buf, int bufsize) @if(!env::WIN32); extern fn void* realloc(void* ptr, usz size); extern fn CInt remove(ZString filename); extern fn CInt rename(ZString old_name, ZString new_name); @@ -184,10 +183,10 @@ extern fn ZString tmpnam(char *buffer); extern fn CFile tmpfile(); extern fn CInt ungetc(CInt c, CFile stream); extern fn CInt unsetenv(ZString name) @if(!env::NETBSD); -extern fn isz write(Fd fd, void* buffer, usz count) @if(!env::WIN32); +extern fn sz write(Fd fd, void* buffer, usz count) @if(!env::WIN32); extern fn CFile fmemopen(void* ptr, usz size, ZString mode); -extern fn isz getline(char** linep, usz* linecapp, CFile stream); +extern fn sz getline(char** linep, usz* linecapp, CFile stream); extern fn CInt timespec_get(TimeSpec* ts, CInt base); extern fn CInt nanosleep(TimeSpec* req, TimeSpec* remaining) @if(!env::NETBSD); extern fn ZString ctime(Time_t* timer); @@ -212,8 +211,8 @@ module libc @if(env::NETBSD || env::OPENBSD); extern fn int fcntl(CInt socket, int cmd, ...); extern fn int _setjmp(void*); macro int setjmp(void* ptr) => _setjmp(ptr); -extern fn int _longjmp(void*, int); -macro usz longjmp(void* ptr, CInt i) => _longjmp(ptr, i); +extern fn void _longjmp(void*, int); +macro void longjmp(void* ptr, CInt i) => _longjmp(ptr, i); extern fn usz malloc_size(void* ptr); extern fn void* aligned_alloc(usz align, usz size); extern fn Tm* gmtime_r(Time_t* timer, Tm* buf) @cname("__gmtime_r50") @if(env::NETBSD); @@ -448,313 +447,328 @@ const int TIME_UTC = 1; const CLOCKS_PER_SEC @if(env::WIN32) = 1000; const CLOCKS_PER_SEC @if(!env::WIN32) = 1000000; -module libc::errno; - -const Errno OK = 0; -const Errno EPERM = 1; // Operation not permitted -const Errno ENOENT = 2; // No such file or directory -const Errno ESRCH = 3; // No such process -const Errno EINTR = 4; // Interrupted system call -const Errno EIO = 5; // I/O error -const Errno ENXIO = 6; // No such device or address -const Errno E2BIG = 7; // Argument list too long -const Errno ENOEXEC = 8; // Exec format error -const Errno EBADF = 9; // Bad file number -const Errno ECHILD = 10; // No child processes - - -const Errno EAGAIN @if(env::DARWIN || env::NETBSD) = 35; // Try again Macos -const Errno EAGAIN @if(!env::DARWIN && !env::NETBSD) = 11; // Try again - -const Errno ENOMEM = 12; // Out of memory -const Errno EACCES = 13; // Permission denied -const Errno EFAULT = 14; // Bad address -const Errno ENOTBLK = 15; // Block device required, not on Win32 -const Errno EBUSY = 16; // Device or resource busy -const Errno EEXIST = 17; // File exists -const Errno EXDEV = 18; // Cross-device link -const Errno ENODEV = 19; // No such device -const Errno ENOTDIR = 20; // Not a directory -const Errno EISDIR = 21; // Is a directory -const Errno EINVAL = 22; // Invalid argument -const Errno ENFILE = 23; // File table overflow -const Errno EMFILE = 24; // Too many open files -const Errno ENOTTY = 25; // Not a typewriter -const Errno ETXTBSY = 26; // Text file busy, not on Win32 -const Errno EFBIG = 27; // File too large -const Errno ENOSPC = 28; // No space left on device -const Errno ESPIPE = 29; // Illegal seek -const Errno EROFS = 30; // Read-only file system -const Errno EMLINK = 31; // Too many links -const Errno EPIPE = 32; // Broken pipe -const Errno EDOM = 33; // Math argument out of domain of func -const Errno ERANGE = 34; // Math result not representable - -// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/intro.2.html -module libc::errno @if(env::DARWIN); -const Errno EWOULDBLOCK = EAGAIN; // Operation would block -const Errno EDEADLK = 11; // Resource deadlock would occur -const Errno EINPROGRESS = 36; // Operation now in progress -const Errno EALREADY = 37; // Operation already in progress -const Errno ENOTSOCK = 38; // Socket operation on non-socket -const Errno EDESTADDRREQ = 39; // Destination address required -const Errno EMSGSIZE = 40; // Message too long -const Errno EPROTOTYPE = 41; // Protocol wrong type for socket -const Errno ENOPROTOOPT = 42; // Protocol not available -const Errno EPROTONOSUPPORT = 43; // Protocol not supported -const Errno ESOCKTNOSUPPORT = 44; // Socket type not supported -const Errno ENOTSUP = 45; // Not supported -const Errno EPFNOSUPPORT = 46; // Protocol family not supported -const Errno EAFNOSUPPORT = 47; // Address family not supported by protocol family -const Errno EADDRINUSE = 48; // Address already in use -const Errno EADDRNOTAVAIL = 49; // Cannot assign requested address -const Errno ENETDOWN = 50; // Network is down -const Errno ENETUNREACH = 51; // Network is unreachable -const Errno ENETRESET = 52; // Network dropped connection on reset -const Errno ECONNABORTED = 53; // Software caused connection abort -const Errno ECONNRESET = 54; // Connection reset by peer -const Errno ENOBUFS = 55; // No buffer space available -const Errno EISCONN = 56; // Socket is already connected -const Errno ENOTCONN = 57; // Socket is not connected -const Errno ESHUTDOWN = 58; // Cannot send after socket shutdown -const Errno ETIMEDOUT = 60; // Operation timed out -const Errno ECONNREFUSED = 61; // Connection refused -const Errno ELOOP = 62; // Too many levels of symbolic links -const Errno ENAMETOOLONG = 63; // File name too long -const Errno EHOSTDOWN = 64; // Host is down -const Errno EHOSTUNREACH = 65; // No route to host -const Errno ENOTEMPTY = 66; // Directory not empty -const Errno EPROCLIM = 67; // Too many processes -const Errno EUSERS = 68; // Too many users -const Errno EDQUOT = 69; // Disc quota exceeded -const Errno ESTALE = 70; // Stale NFS file handle -const Errno EBADRPC = 72; // RPC struct is bad -const Errno ERPCMISMATCH = 73; // RPC version wrong -const Errno EPROGUNAVAIL = 74; // RPC prog. not avail -const Errno EPROGMISMATCH = 75; // Program version wrong -const Errno EPROCUNAVAIL = 76; // Bad procedure for program -const Errno ENOLCK = 77; // No locks available -const Errno ENOSYS = 78; // Function not implemented -const Errno EFTYPE = 79; // Inappropriate file type or format -const Errno EAUTH = 80; // Authentication error -const Errno ENEEDAUTH = 81; // Need authenticator -const Errno EPWROFF = 82; // Device power is off -const Errno EDEVERR = 83; // Device error -const Errno EOVERFLOW = 84; // Value too large to be stored in data type -const Errno EBADEXEC = 85; // Bad executable (or shared library) -const Errno EBADARCH = 86; // Bad CPU type in executable -const Errno ESHLIBVERS = 87; // Shared library version mismatch -const Errno EBADMACHO = 88; // Malformed Mach-o file -const Errno ECANCELED = 89; // Operation canceled -const Errno EIDRM = 90; // Identifier removed -const Errno ENOMSG = 91; // No message of desired type -const Errno EILSEQ = 92; // Illegal byte sequence -const Errno ENOATTR = 93; // Attribute not found -const Errno EBADMSG = 94; // Bad message -const Errno EMULTIHOP = 95; // Reserved -const Errno ENODATA = 96; // No message available -const Errno ENOLINK = 97; // Reserved -const Errno ENOSR = 98; // No STREAM resources -const Errno ENOSTR = 99; // Not a STREAM -const Errno EPROTO = 100; // Protocol error -const Errno ETIME = 101; // STREAM ioctl() timeout -const Errno EOPNOTSUPP = 102; // Operation not supported on socket - -module libc::errno @if(env::NETBSD); -const Errno EWOULDBLOCK = EAGAIN; // Operation would block -const Errno EDEADLK = 11; // Resource deadlock avoided -const Errno EINPROGRESS = 36; // Operation now in progress -const Errno EALREADY = 37; // Operation already in progress -const Errno ENOTSOCK = 38; // Socket operation on non-socket -const Errno EDESTADDRREQ = 39; // Destination address required -const Errno EMSGSIZE = 40; // Message too long -const Errno EPROTOTYPE = 41; // Protocol wrong type for socket -const Errno ENOPROTOOPT = 42; // Protocol option not available -const Errno EPROTONOSUPPORT = 43; // Protocol not supported -const Errno ESOCKTNOSUPPORT = 44; // Socket type not supported -const Errno EOPNOTSUPP = 45; // Operation not supported -const Errno EPFNOSUPPORT = 46; // Protocol family not supported -const Errno EAFNOSUPPORT = 47; // Address family not supported by protocol family -const Errno EADDRINUSE = 48; // Address already in use -const Errno EADDRNOTAVAIL = 49; // Can't assign requested address -const Errno ENETDOWN = 50; // Network is down -const Errno ENETUNREACH = 51; // Network is unreachable -const Errno ENETRESET = 52; // Network dropped connection on reset -const Errno ECONNABORTED = 53; // Software caused connection abort -const Errno ECONNRESET = 54; // Connection reset by peer -const Errno ENOBUFS = 55; // No buffer space available -const Errno EISCONN = 56; // Socket is already connected -const Errno ENOTCONN = 57; // Socket is not connected -const Errno ESHUTDOWN = 58; // Can't send after socket shutdown -const Errno ETOOMANYREFS = 59; // Too many references: can't splice -const Errno ETIMEDOUT = 60; // Operation timed out -const Errno ECONNREFUSED = 61; // Connection refused -const Errno ELOOP = 62; // Too many levels of symbolic links -const Errno ENAMETOOLONG = 63; // File name too long -const Errno EHOSTDOWN = 64; // Host is down -const Errno EHOSTUNREACH = 65; // No route to host -const Errno ENOTEMPTY = 66; // Directory not empty -const Errno EPROCLIM = 67; // Too many processes -const Errno EUSERS = 68; // Too many users -const Errno EDQUOT = 69; // Disc quota exceeded -const Errno ESTALE = 70; // Stale NFS file handle -const Errno EREMOTE = 71; // Too many levels of remote in path -const Errno EBADRPC = 72; // RPC struct is bad -const Errno ERPCMISMATCH = 73; // RPC version wrong -const Errno EPROGUNAVAIL = 74; // RPC prog. not avail -const Errno EPROGMISMATCH = 75; // Program version wrong -const Errno EPROCUNAVAIL = 76; // Bad procedure for program -const Errno ENOLCK = 77; // No locks available -const Errno ENOSYS = 78; // Function not implemented -const Errno EFTYPE = 79; // Inappropriate file type or format -const Errno EAUTH = 80; // Authentication error -const Errno ENEEDAUTH = 81; // Need authenticator -const Errno EIDRM = 82; // Identifier removed -const Errno ENOMSG = 83; // No message of desired type -const Errno EOVERFLOW = 84; // Value too large to be stored in data type -const Errno EILSEQ = 85; // Illegal byte sequence -const Errno ENOTSUP = 86; // Not supported -const Errno ECANCELED = 87; // Operation canceled -const Errno EBADMSG = 88; // Bad or Corrupt message -const Errno ENODATA = 89; // No message available -const Errno ENOSR = 90; // No STREAM resources -const Errno ENOSTR = 91; // Not a STREAM -const Errno ETIME = 92; // STREAM ioctl timeout -const Errno ENOATTR = 93; // Attribute not found -const Errno EMULTIHOP = 94; // Multihop attempted -const Errno ENOLINK = 95; // Link has been severed -const Errno EPROTO = 96; // Protocol error -const Errno EOWNERDEAD = 97; // Previous owner died -const Errno ENOTRECOVERABLE = 98; // State not recoverable - -module libc::errno @if(env::WIN32); -const Errno EDEADLK = 36; // Resource deadlock would occur Win32 -const Errno ENAMETOOLONG = 38; // File name too long Win32 -const Errno ENOTEMPTY = 41; // Directory not empty -const Errno ELOOP = 114; // Too many symbolic links encountered -const Errno EOVERFLOW = 132; // Value too large for defined data type -const Errno ENETDOWN = 116; // Network is down -const Errno ECONNRESET = 108; // Connection reset by peer -const Errno ENETUNREACH = 118; // Network is unreachable -const Errno ENETRESET = 117; // Network dropped connection because of reset -const Errno EOPNOTSUPP = 130; // Operation not supported on transport endpoint -const Errno ETIMEDOUT = 138; // Connection timed out -const Errno EALREADY = 103; // Operation already in progress -const Errno EINPROGRESS = 112; // Operation now in progress Win32 -const Errno EDQUOT = -122; // Quota exceeded, not in Win32 -const Errno EWOULDBLOCK = 140; // Operation would block - -module libc::errno @if(!env::WIN32 && !env::DARWIN && !env::NETBSD); -const Errno EDEADLK = 35; // Resource deadlock would occur Linux (others?) -const Errno ENAMETOOLONG = 36; // File name too long Linux (others?) -const Errno ENOTEMPTY = 39; // Directory not empty -const Errno ELOOP = 40; // Too many symbolic links encountered -const Errno EWOULDBLOCK = EAGAIN; // Operation would block -const Errno EOVERFLOW = 75; // Value too large for defined data type -const Errno ENOTSOCK = 88; // Socket operation on non-socket -const Errno EOPNOTSUPP = 95; // Operation not supported on transport endpoint -const Errno EADDRINUSE = 98; // Address already in use -const Errno EADDRNOTAVAIL = 99; // Cannot assign requested address -const Errno ENETDOWN = 100; // Network is down -const Errno ENETUNREACH = 101; // Network is unreachable -const Errno ENETRESET = 102; // Network dropped connection because of reset -const Errno ECONNRESET = 104; // Connection reset by peer -const Errno EISCONN = 106; // Socket is already connected -const Errno ETIMEDOUT = 110; // Connection timed out -const Errno ECONNREFUSED = 111; // Connection refused -const Errno EALREADY = 114; // Operation already in progress -const Errno EINPROGRESS = 115; // Operation now in progress -const Errno EDQUOT = 122; // Quota exceeded - - -/* -const Errno ENOLCK = 37; /* No record locks available */ -const Errno ENOSYS = 38; /* Function not implemented */ - -const Errno ENOMSG = 42; /* No message of desired type */ -const Errno EIDRM = 43; /* Identifier removed */ -const Errno ECHRNG = 44; /* Channel number out of range */ -const Errno EL2NSYNC = 45; /* Level 2 not synchronized */ -const Errno EL3HLT = 46; /* Level 3 halted */ -const Errno EL3RST = 47; /* Level 3 reset */ -const Errno ELNRNG = 48; /* Link number out of range */ -const Errno EUNATCH = 49; /* Protocol driver not attached */ -const Errno ENOCSI = 50; /* No CSI structure available */ -const Errno EL2HLT = 51; /* Level 2 halted */ -const Errno EBADE = 52; /* Invalid exchange */ -const Errno EBADR = 53; /* Invalid request descriptor */ -const Errno EXFULL = 54; /* Exchange full */ -const Errno ENOANO = 55; /* No anode */ -const Errno EBADRQC = 56; /* Invalid request code */ -const Errno EBADSLT = 57; /* Invalid slot */ - -const Errno EBFONT = 59; /* Bad font file format */ -const Errno ENOSTR = 60; /* Device not a stream */ -const Errno ENODATA = 61; /* No data available */ -const Errno ETIME = 62; /* Timer expired */ -const Errno ENOSR = 63; /* Out of streams resources */ -const Errno ENONET = 64; /* Machine is not on the network */ -const Errno ENOPKG = 65; /* Package not installed */ -const Errno EREMOTE = 66; /* Object is remote */ -const Errno ENOLINK = 67; /* Link has been severed */ -const Errno EADV = 68; /* Advertise error */ -const Errno ESRMNT = 69; /* Srmount error */ -const Errno ECOMM = 70; /* Communication error on send */ -const Errno EPROTO = 71; /* Protocol error */ -const Errno EMULTIHOP = 72; /* Multihop attempted */ -const Errno EDOTDOT = 73; /* RFS specific error */ -const Errno EBADMSG = 74; /* Not a data message */ -const Errno ENOTUNIQ = 76; /* Name not unique on network */ -const Errno EBADFD = 77; /* File descriptor in bad state */ -const Errno EREMCHG = 78; /* Remote address changed */ -const Errno ELIBACC = 79; /* Can not access a needed shared library */ -const Errno ELIBBAD = 80; /* Accessing a corrupted shared library */ -const Errno ELIBSCN = 81; /* .lib section in a.out corrupted */ -const Errno ELIBMAX = 82; /* Attempting to link in too many shared libraries */ -const Errno ELIBEXEC = 83; /* Cannot exec a shared library directly */ -const Errno EILSEQ = 84; /* Illegal byte sequence */ -const Errno ERESTART = 85; /* Interrupted system call should be restarted */ -const Errno ESTRPIPE = 86; /* Streams pipe error */ -const Errno EUSERS = 87; /* Too many users */ -const Errno ENOTSOCK = 88; /* Socket operation on non-socket */ -const Errno EDESTADDRREQ = 89; /* Destination address required */ -const Errno EMSGSIZE = 90; /* Message too long */ -const Errno EPROTOTYPE = 91; /* Protocol wrong type for socket */ -const Errno ENOPROTOOPT = 92; /* Protocol not available */ -const Errno EPROTONOSUPPORT = 93; /* Protocol not supported */ -const Errno ESOCKTNOSUPPORT = 94; /* Socket type not supported */ -const Errno EPFNOSUPPORT = 96; /* Protocol family not supported */ -const Errno EAFNOSUPPORT = 97; /* Address family not supported by protocol */ -const Errno EADDRINUSE = 98; /* Address already in use */ -const Errno ECONNABORTED = 103; /* Software caused connection abort */ -const Errno ENOBUFS = 105; /* No buffer space available */ -const Errno EISCONN = 106; /* Transport endpoint is already connected */ -const Errno ENOTCONN = 107; /* Transport endpoint is not connected */ -const Errno ESHUTDOWN = 108; /* Cannot send after transport endpoint shutdown */ -const Errno ETOOMANYREFS = 109; /* Too many references: cannot splice */ -const Errno ECONNREFUSED = 111; /* Connection refused */ -const Errno EHOSTDOWN = 112; /* Host is down */ -const Errno EHOSTUNREACH = 113; /* No route to host */ -*/ - - -/* -const Errno ESTALE = 116; /* Stale NFS file handle */ -const Errno EUCLEAN = 117; /* Structure needs cleaning */ -const Errno ENOTNAM = 118; /* Not a XENIX named type file */ -const Errno ENAVAIL = 119; /* No XENIX semaphores available */ -const Errno EISNAM = 120; /* Is a named type file */ -const Errno EREMOTEIO = 121; /* Remote I/O error */ - -const Errno ENOMEDIUM = 123; /* No medium found */ -const Errno EMEDIUMTYPE = 124; /* Wrong medium type */ -const Errno ECANCELED = 125; /* Operation Canceled */ -const Errno ENOKEY = 126; /* Required key not available */ -const Errno EKEYEXPIRED = 127; /* Key has expired */ -const Errno EKEYREVOKED = 128; /* Key has been revoked */ -const Errno EKEYREJECTED = 129; /* Key was rejected by service */ - -const Errno EOWNERDEAD = 130; /* Owner died */ -const Errno ENOTRECOVERABLE = 131; /* State not recoverable */ -*/ +constdef Errno : inline CInt @if(env::DARWIN) +{ + OK = 0, + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // I/O error + ENXIO = 6, // No such device or address + E2BIG = 7, // Argument list too long + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file number + ECHILD = 10, // No child processes + EDEADLK = 11, // Resource deadlock would occur + ENOMEM = 12, // Out of memory + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + ENOTBLK = 15, // Block device required, not on Win32 + EBUSY = 16, // Device or resource busy + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENODEV = 19, // No such device + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + ENFILE = 23, // File table overflow + EMFILE = 24, // Too many open files + ENOTTY = 25, // Not a typewriter + ETXTBSY = 26, // Text file busy, not on Win32 + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EROFS = 30, // Read-only file system + EMLINK = 31, // Too many links + EPIPE = 32, // Broken pipe + EDOM = 33, // Math argument out of domain of func + ERANGE = 34, // Math result not representable + EAGAIN = 35, // Try again + EWOULDBLOCK = EAGAIN, // Operation would block + EINPROGRESS = 36, // Operation now in progress + EALREADY = 37, // Operation already in progress + ENOTSOCK = 38, // Socket operation on non-socket + EDESTADDRREQ = 39, // Destination address required + EMSGSIZE = 40, // Message too long + EPROTOTYPE = 41, // Protocol wrong type for socket + ENOPROTOOPT = 42, // Protocol not available + EPROTONOSUPPORT = 43, // Protocol not supported + ESOCKTNOSUPPORT = 44, // Socket type not supported + ENOTSUP = 45, // Not supported + EPFNOSUPPORT = 46, // Protocol family not supported + EAFNOSUPPORT = 47, // Address family not supported by protocol family + EADDRINUSE = 48, // Address already in use + EADDRNOTAVAIL = 49, // Cannot assign requested address + ENETDOWN = 50, // Network is down + ENETUNREACH = 51, // Network is unreachable + ENETRESET = 52, // Network dropped connection on reset + ECONNABORTED = 53, // Software caused connection abort + ECONNRESET = 54, // Connection reset by peer + ENOBUFS = 55, // No buffer space available + EISCONN = 56, // Socket is already connected + ENOTCONN = 57, // Socket is not connected + ESHUTDOWN = 58, // Cannot send after socket shutdown + ETIMEDOUT = 60, // Operation timed out + ECONNREFUSED = 61, // Connection refused + ELOOP = 62, // Too many levels of symbolic links + ENAMETOOLONG = 63, // File name too long + EHOSTDOWN = 64, // Host is down + EHOSTUNREACH = 65, // No route to host + ENOTEMPTY = 66, // Directory not empty + EPROCLIM = 67, // Too many processes + EUSERS = 68, // Too many users + EDQUOT = 69, // Disc quota exceeded + ESTALE = 70, // Stale NFS file handle + EBADRPC = 72, // RPC struct is bad + ERPCMISMATCH = 73, // RPC version wrong + EPROGUNAVAIL = 74, // RPC prog. not avail + EPROGMISMATCH = 75, // Program version wrong + EPROCUNAVAIL = 76, // Bad procedure for program + ENOLCK = 77, // No locks available + ENOSYS = 78, // Function not implemented + EFTYPE = 79, // Inappropriate file type or format + EAUTH = 80, // Authentication error + ENEEDAUTH = 81, // Need authenticator + EPWROFF = 82, // Device power is off + EDEVERR = 83, // Device error + EOVERFLOW = 84, // Value too large to be stored in data type + EBADEXEC = 85, // Bad executable (or shared library) + EBADARCH = 86, // Bad CPU type in executable + ESHLIBVERS = 87, // Shared library version mismatch + EBADMACHO = 88, // Malformed Mach-o file + ECANCELED = 89, // Operation canceled + EIDRM = 90, // Identifier removed + ENOMSG = 91, // No message of desired type + EILSEQ = 92, // Illegal byte sequence + ENOATTR = 93, // Attribute not found + EBADMSG = 94, // Bad message + EMULTIHOP = 95, // Reserved + ENODATA = 96, // No message available + ENOLINK = 97, // Reserved + ENOSR = 98, // No STREAM resources + ENOSTR = 99, // Not a STREAM + EPROTO = 100, // Protocol error + ETIME = 101, // STREAM ioctl() timeout + EOPNOTSUPP = 102, // Operation not supported on socket +} + +constdef Errno : inline CInt @if(env::NETBSD) +{ + OK = 0, + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // I/O error + ENXIO = 6, // No such device or address + E2BIG = 7, // Argument list too long + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file number + ECHILD = 10, // No child processes + EDEADLK = 11, // Resource deadlock avoided + ENOMEM = 12, // Out of memory + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + ENOTBLK = 15, // Block device required, not on Win32 + EBUSY = 16, // Device or resource busy + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENODEV = 19, // No such device + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + ENFILE = 23, // File table overflow + EMFILE = 24, // Too many open files + ENOTTY = 25, // Not a typewriter + ETXTBSY = 26, // Text file busy, not on Win32 + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EROFS = 30, // Read-only file system + EMLINK = 31, // Too many links + EPIPE = 32, // Broken pipe + EDOM = 33, // Math argument out of domain of func + ERANGE = 34, // Math result not representable + EAGAIN = 35, // Try again + EWOULDBLOCK = EAGAIN, // Operation would block + EINPROGRESS = 36, // Operation now in progress + EALREADY = 37, // Operation already in progress + ENOTSOCK = 38, // Socket operation on non-socket + EDESTADDRREQ = 39, // Destination address required + EMSGSIZE = 40, // Message too long + EPROTOTYPE = 41, // Protocol wrong type for socket + ENOPROTOOPT = 42, // Protocol option not available + EPROTONOSUPPORT = 43, // Protocol not supported + ESOCKTNOSUPPORT = 44, // Socket type not supported + EOPNOTSUPP = 45, // Operation not supported + EPFNOSUPPORT = 46, // Protocol family not supported + EAFNOSUPPORT = 47, // Address family not supported by protocol family + EADDRINUSE = 48, // Address already in use + EADDRNOTAVAIL = 49, // Can't assign requested address + ENETDOWN = 50, // Network is down + ENETUNREACH = 51, // Network is unreachable + ENETRESET = 52, // Network dropped connection on reset + ECONNABORTED = 53, // Software caused connection abort + ECONNRESET = 54, // Connection reset by peer + ENOBUFS = 55, // No buffer space available + EISCONN = 56, // Socket is already connected + ENOTCONN = 57, // Socket is not connected + ESHUTDOWN = 58, // Can't send after socket shutdown + ETOOMANYREFS = 59, // Too many references: can't splice + ETIMEDOUT = 60, // Operation timed out + ECONNREFUSED = 61, // Connection refused + ELOOP = 62, // Too many levels of symbolic links + ENAMETOOLONG = 63, // File name too long + EHOSTDOWN = 64, // Host is down + EHOSTUNREACH = 65, // No route to host + ENOTEMPTY = 66, // Directory not empty + EPROCLIM = 67, // Too many processes + EUSERS = 68, // Too many users + EDQUOT = 69, // Disc quota exceeded + ESTALE = 70, // Stale NFS file handle + EREMOTE = 71, // Too many levels of remote in path + EBADRPC = 72, // RPC struct is bad + ERPCMISMATCH = 73, // RPC version wrong + EPROGUNAVAIL = 74, // RPC prog. not avail + EPROGMISMATCH = 75, // Program version wrong + EPROCUNAVAIL = 76, // Bad procedure for program + ENOLCK = 77, // No locks available + ENOSYS = 78, // Function not implemented + EFTYPE = 79, // Inappropriate file type or format + EAUTH = 80, // Authentication error + ENEEDAUTH = 81, // Need authenticator + EIDRM = 82, // Identifier removed + ENOMSG = 83, // No message of desired type + EOVERFLOW = 84, // Value too large to be stored in data type + EILSEQ = 85, // Illegal byte sequence + ENOTSUP = 86, // Not supported + ECANCELED = 87, // Operation canceled + EBADMSG = 88, // Bad or Corrupt message + ENODATA = 89, // No message available + ENOSR = 90, // No STREAM resources + ENOSTR = 91, // Not a STREAM + ETIME = 92, // STREAM ioctl timeout + ENOATTR = 93, // Attribute not found + EMULTIHOP = 94, // Multihop attempted + ENOLINK = 95, // Link has been severed + EPROTO = 96, // Protocol error + EOWNERDEAD = 97, // Previous owner died + ENOTRECOVERABLE = 98, // State not recoverable + +} + +constdef Errno : inline CInt @if(env::WIN32) +{ + OK = 0, + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // I/O error + ENXIO = 6, // No such device or address + E2BIG = 7, // Argument list too long + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file number + ECHILD = 10, // No child processes + ENOMEM = 12, // Out of memory + EAGAIN = 11, // Try again + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + ENOTBLK = 15, // Block device required, not on Win32 + EBUSY = 16, // Device or resource busy + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENODEV = 19, // No such device + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + ENFILE = 23, // File table overflow + EMFILE = 24, // Too many open files + ENOTTY = 25, // Not a typewriter + ETXTBSY = 26, // Text file busy, not on Win32 + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EROFS = 30, // Read-only file system + EMLINK = 31, // Too many links + EPIPE = 32, // Broken pipe + EDOM = 33, // Math argument out of domain of func + ERANGE = 34, // Math result not representable + EDEADLK = 36, // Resource deadlock would occur Win32 + ENAMETOOLONG = 38, // File name too long Win32 + ENOTEMPTY = 41, // Directory not empty + ELOOP = 114, // Too many symbolic links encountered + EOVERFLOW = 132, // Value too large for defined data type + ENETDOWN = 116, // Network is down + ECONNRESET = 108, // Connection reset by peer + ENETUNREACH = 118, // Network is unreachable + ENETRESET = 117, // Network dropped connection because of reset + EOPNOTSUPP = 130, // Operation not supported on transport endpoint + ETIMEDOUT = 138, // Connection timed out + EALREADY = 103, // Operation already in progress + EINPROGRESS = 112, // Operation now in progress Win32 + EDQUOT = -122, // Quota exceeded, not in Win32 + EWOULDBLOCK = 140, // Operation would block +} + + +constdef Errno : inline CInt @if(!env::WIN32 && !env::DARWIN && !env::NETBSD) +{ + OK = 0, + EPERM = 1, // Operation not permitted + ENOENT = 2, // No such file or directory + ESRCH = 3, // No such process + EINTR = 4, // Interrupted system call + EIO = 5, // I/O error + ENXIO = 6, // No such device or address + E2BIG = 7, // Argument list too long + ENOEXEC = 8, // Exec format error + EBADF = 9, // Bad file number + ECHILD = 10, // No child processes + ENOMEM = 12, // Out of memory + EAGAIN = 11, // Try again + EACCES = 13, // Permission denied + EFAULT = 14, // Bad address + ENOTBLK = 15, // Block device required, not on Win32 + EBUSY = 16, // Device or resource busy + EEXIST = 17, // File exists + EXDEV = 18, // Cross-device link + ENODEV = 19, // No such device + ENOTDIR = 20, // Not a directory + EISDIR = 21, // Is a directory + EINVAL = 22, // Invalid argument + ENFILE = 23, // File table overflow + EMFILE = 24, // Too many open files + ENOTTY = 25, // Not a typewriter + ETXTBSY = 26, // Text file busy, not on Win32 + EFBIG = 27, // File too large + ENOSPC = 28, // No space left on device + ESPIPE = 29, // Illegal seek + EROFS = 30, // Read-only file system + EMLINK = 31, // Too many links + EPIPE = 32, // Broken pipe + EDOM = 33, // Math argument out of domain of func + ERANGE = 34, // Math result not representable + EDEADLK = 35, // Resource deadlock would occur Linux (others?) + ENAMETOOLONG = 36, // File name too long Linux (others?) + ENOTEMPTY = 39, // Directory not empty + ELOOP = 40, // Too many symbolic links encountered + EWOULDBLOCK = EAGAIN, // Operation would block + EOVERFLOW = 75, // Value too large for defined data type + ENOTSOCK = 88, // Socket operation on non-socket + EOPNOTSUPP = 95, // Operation not supported on transport endpoint + EADDRINUSE = 98, // Address already in use + EADDRNOTAVAIL = 99, // Cannot assign requested address + ENETDOWN = 100, // Network is down + ENETUNREACH = 101, // Network is unreachable + ENETRESET = 102, // Network dropped connection because of reset + ECONNRESET = 104, // Connection reset by peer + EISCONN = 106, // Socket is already connected + ETIMEDOUT = 110, // Connection timed out + ECONNREFUSED = 111, // Connection refused + EALREADY = 114, // Operation already in progress + EINPROGRESS = 115, // Operation now in progress + EDQUOT = 122, // Quota exceeded +} diff --git a/test/stdlib/src/libc/libc_extra.c3 b/test/stdlib/src/libc/libc_extra.c3 index 028fc3b..ab19420 100644 --- a/test/stdlib/src/libc/libc_extra.c3 +++ b/test/stdlib/src/libc/libc_extra.c3 @@ -10,7 +10,7 @@ fn TimeSpec NanoDuration.to_timespec(self) @inline { CLong ns = (CLong)(self % 1000_000_000); Time_t sec = (Time_t)(self / 1000_000_000); - return { .s = sec, .ns = ns }; + return { .s = sec, .ns = ($typeof((TimeSpec){}.ns))ns }; } <* @@ -22,7 +22,7 @@ fn TimeSpec Duration.to_timespec(self) @inline { CLong ns = (CLong)(1000 * (self % time::SEC)); Time_t sec = (Time_t)(self / time::SEC); - return { .s = sec, .ns = ns }; + return { .s = sec, .ns = ($typeof((TimeSpec){}.ns))ns }; } <* diff --git a/test/stdlib/src/libc/os/android.c3 b/test/stdlib/src/libc/os/android.c3 index 8cc2856..401042e 100644 --- a/test/stdlib/src/libc/os/android.c3 +++ b/test/stdlib/src/libc/os/android.c3 @@ -2,8 +2,8 @@ module libc @if(env::ANDROID); // Checked for x86_64, this is notoriously incorrect when comparing with Rust code etc -alias Blksize_t = $typefrom(env::X86_64 ? long.typeid : CInt.typeid); -alias Nlink_t = $typefrom(env::X86_64 ? ulong.typeid : CUInt.typeid); +alias Blksize_t = $typefrom(env::X86_64 ? long::typeid : CInt::typeid); +alias Nlink_t = $typefrom(env::X86_64 ? ulong::typeid : CUInt::typeid); alias Blkcnt_t = long; alias Ino_t = ulong; alias Dev_t = ulong; diff --git a/test/stdlib/src/libc/os/darwin.c3 b/test/stdlib/src/libc/os/darwin.c3 index a6a971c..d96d085 100644 --- a/test/stdlib/src/libc/os/darwin.c3 +++ b/test/stdlib/src/libc/os/darwin.c3 @@ -44,3 +44,7 @@ struct Stat extern fn int stat(ZString str, Stat* stat) @cname("stat64"); extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen); + +const SCHED_OTHER = 1; +const SCHED_FIFO = 4; +const SCHED_RR = 2; \ No newline at end of file diff --git a/test/stdlib/src/libc/os/freebsd.c3 b/test/stdlib/src/libc/os/freebsd.c3 index 5884ac1..3b4edd6 100644 --- a/test/stdlib/src/libc/os/freebsd.c3 +++ b/test/stdlib/src/libc/os/freebsd.c3 @@ -3,7 +3,7 @@ module libc @if(env::FREEBSD); // Checked for x86_64 alias Blksize_t = int; -alias Nlink_t = $typefrom(env::X86_64 ? ulong.typeid : CUInt.typeid); +alias Nlink_t = $typefrom(env::X86_64 ? ulong::typeid : CUInt::typeid); alias Dev_t = ulong; alias Ino_t = ulong; alias Mode_t = ushort; @@ -61,3 +61,9 @@ extern fn CInt stat(ZString path, Stat* stat); extern fn CInt get_nprocs(); extern fn CInt get_nprocs_conf(); + +extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen); + +const SCHED_OTHER = 2; +const SCHED_FIFO = 1; +const SCHED_RR = 3; \ No newline at end of file diff --git a/test/stdlib/src/libc/os/linux.c3 b/test/stdlib/src/libc/os/linux.c3 index 328a8b7..721ac68 100644 --- a/test/stdlib/src/libc/os/linux.c3 +++ b/test/stdlib/src/libc/os/linux.c3 @@ -2,8 +2,8 @@ module libc @if(env::LINUX); // Checked for x86_64, this is notoriously incorrect when comparing with Rust code etc -alias Blksize_t = $typefrom(env::X86_64 ? long.typeid : CInt.typeid); -alias Nlink_t = $typefrom(env::X86_64 ? ulong.typeid : CUInt.typeid); +alias Blksize_t = $typefrom(env::X86_64 ? long::typeid : CInt::typeid); +alias Nlink_t = $typefrom(env::X86_64 ? ulong::typeid : CUInt::typeid); alias Blkcnt_t = long; alias Ino_t = ulong; alias Dev_t = ulong; @@ -69,3 +69,34 @@ const CInt SIGIO = 29; const CInt SIGPOLL = 29; const CInt SIGPWR = 30; const CInt SIGUNUSED = 31; + +module libc @if(env::LINUX || env::ANDROID); + +const PRIO_MIN = -20; +const PRIO_MAX = 20; +const PRIO_PROCESS = 0; +const PRIO_PGRP = 1; +const PRIO_USER = 2; + +const SCHED_OTHER = 0; +const SCHED_FIFO = 1; +const SCHED_RR = 2; +const SCHED_BATCH = 3; +const SCHED_IDLE = 5; + +const SYS_GETTID = gettid_code(); + +macro gettid_code() @const +{ + $switch env::ARCH_TYPE: + $case X86_64: return 186; + $case X86: + $case ARM: + $case ARMB: return 224; + $case RISCV32: + $case RISCV64: + $case AARCH64: return 178; + $default: $error("Missing gettid code"); + $endswitch +} + diff --git a/test/stdlib/src/libc/os/netbsd.c3 b/test/stdlib/src/libc/os/netbsd.c3 index 8f4a5cd..6bf06e5 100644 --- a/test/stdlib/src/libc/os/netbsd.c3 +++ b/test/stdlib/src/libc/os/netbsd.c3 @@ -36,3 +36,6 @@ extern fn CInt stat(ZString path, Stat* stat) @cname("__stat50"); extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen); +const SCHED_OTHER = 0; +const SCHED_FIFO = 1; +const SCHED_RR = 2; \ No newline at end of file diff --git a/test/stdlib/src/libc/os/openbsd.c3 b/test/stdlib/src/libc/os/openbsd.c3 index 76711a2..7664675 100644 --- a/test/stdlib/src/libc/os/openbsd.c3 +++ b/test/stdlib/src/libc/os/openbsd.c3 @@ -3,7 +3,7 @@ module libc @if(env::OPENBSD); // Checked for x86_64 alias Blksize_t = int; -alias Nlink_t = $typefrom(env::X86_64 ? uint.typeid : CUInt.typeid); +alias Nlink_t = $typefrom(env::X86_64 ? uint::typeid : CUInt::typeid); alias Dev_t = int; alias Ino_t = ulong; alias Mode_t = uint; @@ -58,3 +58,6 @@ extern fn CInt stat(ZString path, Stat* stat); extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen); +const SCHED_OTHER = 2; +const SCHED_FIFO = 1; +const SCHED_RR = 3; \ No newline at end of file diff --git a/test/stdlib/src/libc/os/posix.c3 b/test/stdlib/src/libc/os/posix.c3 index 9cc493f..fc23aab 100644 --- a/test/stdlib/src/libc/os/posix.c3 +++ b/test/stdlib/src/libc/os/posix.c3 @@ -5,8 +5,8 @@ const CInt SHUT_WR = 1; const CInt SHUT_RDWR = 2; extern fn CInt shutdown(Fd sockfd, CInt how); -extern fn isz recv(Fd socket, void *buffer, usz length, CInt flags); -extern fn isz send(Fd socket, void *buffer, usz length, CInt flags); +extern fn sz recv(Fd socket, void *buffer, usz length, CInt flags); +extern fn sz send(Fd socket, void *buffer, usz length, CInt flags); extern fn void* dlopen(ZString path, int flags); extern fn CInt dlclose(void*); @@ -50,6 +50,12 @@ struct Sigaction void* sa_restorer @if(env::LINUX || env::ANDROID); } +extern fn CInt setpriority(CInt which, CInt who, CInt prio); +extern fn CInt getpriority(CInt which, CInt who); + +extern fn CInt sched_get_priority_min(CInt); +extern fn CInt sched_get_priority_max(CInt); + struct Stack_t { void* ss_sp; @@ -312,109 +318,3 @@ struct Termios Speed c_ospeed; } -const Tcactions TCOOFF @deprecated = (Tcactions)0; -const Tcactions TCOON @deprecated = (Tcactions)1; -const Tcactions TCIOFF @deprecated = (Tcactions)2; -const Tcactions TCION @deprecated = (Tcactions)3; -const Tcactions TCIFLUSH @deprecated = (Tcactions)0; -const Tcactions TCOFLUSH @deprecated = (Tcactions)1; -const Tcactions TCIOFLUSH @deprecated = (Tcactions)2; -const Tcactions TCSANOW @deprecated = (Tcactions)0; -const Tcactions TCSADRAIN @deprecated = (Tcactions)1; -const Tcactions TCSAFLUSH @deprecated = (Tcactions)2; -const Speed B0 @deprecated = (Speed)0o0000000; -const Speed B50 @deprecated = (Speed)0o0000001; -const Speed B75 @deprecated = (Speed)0o0000002; -const Speed B110 @deprecated = (Speed)0o0000003; -const Speed B134 @deprecated = (Speed)0o0000004; -const Speed B150 @deprecated = (Speed)0o0000005; -const Speed B200 @deprecated = (Speed)0o0000006; -const Speed B300 @deprecated = (Speed)0o0000007; -const Speed B600 @deprecated = (Speed)0o0000010; -const Speed B1200 @deprecated = (Speed)0o0000011; -const Speed B1800 @deprecated = (Speed)0o0000012; -const Speed B2400 @deprecated = (Speed)0o0000013; -const Speed B4800 @deprecated = (Speed)0o0000014; -const Speed B9600 @deprecated = (Speed)0o0000015; -const Speed B19200 @deprecated = (Speed)0o0000016; -const Speed B38400 @deprecated = (Speed)0o0000017; -const Speed B57600 @deprecated = (Speed)0o0010001; -const Speed B115200 @deprecated = (Speed)0o0010002; -const Speed B230400 @deprecated = (Speed)0o0010003; -const Speed B460800 @deprecated = (Speed)0o0010004; -const Speed B500000 @deprecated = (Speed)0o0010005; -const Speed B576000 @deprecated = (Speed)0o0010006; -const Speed B921600 @deprecated = (Speed)0o0010007; -const Speed B1000000 @deprecated = (Speed)0o0010010; -const Speed B1152000 @deprecated = (Speed)0o0010011; -const Speed B1500000 @deprecated = (Speed)0o0010012; -const Speed B2000000 @deprecated = (Speed)0o0010013; -const Speed B2500000 @deprecated = (Speed)0o0010014; -const Speed B3000000 @deprecated = (Speed)0o0010015; -const Speed B3500000 @deprecated = (Speed)0o0010016; -const Speed B4000000 @deprecated = (Speed)0o0010017; -const Speed MAX_BAUD @deprecated = (Speed)0o0010017; -const Cc VINTR @deprecated = (Cc)0; -const Cc VQUIT @deprecated = (Cc)1; -const Cc VERASE @deprecated = (Cc)2; -const Cc VKILL @deprecated = (Cc)3; -const Cc VEOF @deprecated = (Cc)4; -const Cc VTIME @deprecated = (Cc)5; -const Cc VMIN @deprecated = (Cc)6; -const Cc VSWTC @deprecated = (Cc)7; -const Cc VSTART @deprecated = (Cc)8; -const Cc VSTOP @deprecated = (Cc)9; -const Cc VSUSP @deprecated = (Cc)10; -const Cc VEOL @deprecated = (Cc)11; -const Cc VREPRINT @deprecated = (Cc)12; -const Cc VDISCARD @deprecated = (Cc)13; -const Cc VWERASE @deprecated = (Cc)14; -const Cc VLNEXT @deprecated = (Cc)15; -const Cc VEOL2 @deprecated = (Cc)16; -const Tc_lflags ISIG @deprecated = {.isig}; -const Tc_lflags ICANON @deprecated = {.icanon}; -const Tc_lflags ECHO @deprecated = {.echo}; -const Tc_lflags ECHOE @deprecated = {.echoe}; -const Tc_lflags ECHOK @deprecated = {.echok}; -const Tc_lflags ECHONL @deprecated = {.echonl}; -const Tc_lflags NOFLSH @deprecated = {.noflsh}; -const Tc_lflags TOSTOP @deprecated = {.tostop}; -const Tc_lflags IEXTEN @deprecated = {.iexten}; -const Tc_cflags CSIZE @deprecated = {.csize = CS8}; -const Tc_cflags CS5 @deprecated = {.csize = CS5}; -const Tc_cflags CS6 @deprecated = {.csize = CS6}; -const Tc_cflags CS7 @deprecated = {.csize = CS7}; -const Tc_cflags CS8 @deprecated = {.csize = CS8}; -const Tc_cflags CSTOPB @deprecated = {.cstopb}; -const Tc_cflags CREAD @deprecated = {.cread}; -const Tc_cflags PARENB @deprecated = {.parenb}; -const Tc_cflags PARODD @deprecated = {.parodd}; -const Tc_cflags HUPCL @deprecated = {.hupcl}; -const Tc_cflags CLOCAL @deprecated = {.clocal}; -const Tc_oflags OPOST @deprecated = {.opost}; -const Tc_oflags OLCUC @deprecated = {.olcuc}; -const Tc_oflags ONLCR @deprecated = {.onlcr}; -const Tc_oflags OCRNL @deprecated = {.ocrnl}; -const Tc_oflags ONOCR @deprecated = {.onocr}; -const Tc_oflags ONLRET @deprecated = {.onlret}; -const Tc_oflags OFILL @deprecated = {.ofill}; -const Tc_oflags OFDEL @deprecated = {.ofdel}; -const Tc_oflags VTDLY @deprecated = {.vtdly = VT1}; -const Tc_oflags VT0 @deprecated = {.vtdly = VT0}; -const Tc_oflags VT1 @deprecated = {.vtdly = VT1}; -const Tc_iflags IGNBRK @deprecated = {.ignbrk}; -const Tc_iflags BRKINT @deprecated = {.brkint}; -const Tc_iflags IGNPAR @deprecated = {.ignpar}; -const Tc_iflags PARMRK @deprecated = {.parmrk}; -const Tc_iflags INPCK @deprecated = {.inpck}; -const Tc_iflags ISTRIP @deprecated = {.istrip}; -const Tc_iflags INLCR @deprecated = {.inlcr}; -const Tc_iflags IGNCR @deprecated = {.igncr}; -const Tc_iflags ICRNL @deprecated = {.icrnl}; -const Tc_iflags IUCLC @deprecated = {.iuclc}; -const Tc_iflags IXON @deprecated = {.ixon}; -const Tc_iflags IXANY @deprecated = {.ixany}; -const Tc_iflags IXOFF @deprecated = {.ixoff}; -const Tc_iflags IMAXBEL @deprecated = {.imaxbel}; -const Tc_iflags IUTF8 @deprecated = {.iutf8}; - diff --git a/test/stdlib/src/libc/os/win32.c3 b/test/stdlib/src/libc/os/win32.c3 index 59c797a..081f3e8 100644 --- a/test/stdlib/src/libc/os/win32.c3 +++ b/test/stdlib/src/libc/os/win32.c3 @@ -68,8 +68,8 @@ extern fn CInt get_system_info(SystemInfo*) @cname("GetSystemInfo"); macro Tm* localtime_r(Time_t* timer, Tm* buf) => _localtime64_s(buf, timer); macro CInt setjmp(JmpBuf* buffer) => _setjmp(buffer, null); macro Tm* gmtime_r(Time_t* timer, Tm* buf) => _gmtime64_s(buf, timer); -macro isz read(Fd fd, void* buffer, usz buffer_size) => _read(fd, buffer, (CUInt)buffer_size); -macro isz write(Fd fd, void* buffer, usz count) => _write(fd, buffer, (CUInt)count); +macro sz read(Fd fd, void* buffer, sz buffer_size) => _read(fd, buffer, (CUInt)buffer_size); +macro sz write(Fd fd, void* buffer, sz count) => _write(fd, buffer, (CUInt)count); const CInt SIGABRT_COMPAT = 6; const CInt SIGBREAK = 21; diff --git a/test/stdlib/src/libc/termios.c3 b/test/stdlib/src/libc/termios.c3 index a860872..7c2c694 100644 --- a/test/stdlib/src/libc/termios.c3 +++ b/test/stdlib/src/libc/termios.c3 @@ -11,14 +11,6 @@ fn int Termios.set_ispeed(&self, Speed speed) => cfsetispeed(self, speed); fn int Termios.get_attr(&self, Fd fd) => tcgetattr(fd, self); fn int Termios.set_attr(&self, Fd fd, Tcactions optional_actions) => tcsetattr(fd, optional_actions, self); -fn int sendBreak(Fd fd, int duration) @deprecated => send_break(fd, duration); -fn Speed Termios.getOSpeed(&self) @deprecated => self.get_ospeed(); -fn Speed Termios.getISpeed(&self) @deprecated => self.get_ispeed(); -fn int Termios.setOSpeed(&self, Speed speed) @deprecated => self.set_ospeed(speed); -fn int Termios.setISpeed(&self, Speed speed) @deprecated => self.set_ispeed(speed); -fn int Termios.getAttr(&self, Fd fd) @deprecated => self.get_attr(fd); -fn int Termios.setAttr(&self, Fd fd, Tcactions optional_actions) @deprecated => self.set_attr(fd, optional_actions); - module libc::termios @if(!env::LIBC ||| !env::POSIX); typedef Cc = char; diff --git a/test/stdlib/src/math/bigint.c3 b/test/stdlib/src/math/bigint.c3 index 686049a..05f659a 100644 --- a/test/stdlib/src/math/bigint.c3 +++ b/test/stdlib/src/math/bigint.c3 @@ -13,7 +13,7 @@ const BigInt ONE = { .len = 1, .data[0] = 1 }; struct BigInt (Printable) { uint[MAX_LEN] data; - uint len; + int len; } fn BigInt from_int(int128 val) @@ -27,7 +27,7 @@ fn BigInt* BigInt.init(&self, int128 value) { self.data[..] = 0; int128 tmp = value; - uint len = 0; + int len = 0; while (tmp != 0 && len < MAX_LEN) { self.data[len] = (uint)(tmp & 0xFFFFFFFF); @@ -45,7 +45,7 @@ fn BigInt* BigInt.init_with_u128(&self, uint128 value) { self.data[..] = 0; uint128 tmp = value; - uint len = 0; + int len = 0; while (tmp != 0) { self.data[len] = (uint)(tmp & 0xFFFFFFFF); @@ -85,12 +85,12 @@ fn BigInt* BigInt.init_with_array(&self, uint[] values) fn BigInt*? BigInt.init_string_radix(&self, String value, int radix) { - isz len = value.len; - isz limit = value[0] == '-' ? 1 : 0; + sz len = value.len; + sz limit = value[0] == '-' ? 1 : 0; *self = ZERO; BigInt multiplier = ONE; BigInt radix_big = from_int(radix); - for (isz i = len - 1; i >= limit; i--) + for (sz i = len - 1; i >= limit; i--) { int pos_val = value[i]; switch (pos_val) @@ -141,7 +141,7 @@ fn void BigInt.add_this(&self, BigInt other) @operator(+=) self.len = max(self.len, other.len); ulong carry = 0; - for (uint i = 0; i < self.len; i++) + for (int i = 0; i < self.len; i++) { ulong sum = (ulong)self.data[i] + (ulong)other.data[i] + carry; carry = sum >> 32; @@ -159,7 +159,7 @@ fn void BigInt.add_this(&self, BigInt other) @operator(+=) } -macro uint find_length(uint* data, uint length) +macro int find_length(uint* data, int length) { while (length > 1 && data[length - 1] == 0) { @@ -199,7 +199,7 @@ fn void BigInt.mult_this(&self, BigInt bi2) @operator(*=) } // multiply the absolute values - for (uint i = 0; i < self.len; i++) + for (int i = 0; i < self.len; i++) { if (self.data[i] == 0) continue; ulong mcarry = 0; @@ -220,7 +220,7 @@ fn void BigInt.mult_this(&self, BigInt bi2) @operator(*=) } } - res.len = min(self.len + bi2.len, (uint)MAX_LEN); + res.len = min(self.len + bi2.len, MAX_LEN); reduce_len(&res); @@ -240,7 +240,7 @@ fn void BigInt.negate(&self) bool was_negative = self.is_negative(); // 1's complement - for (uint i = 0; i < MAX_LEN; i++) + for (int i = 0; i < MAX_LEN; i++) { self.data[i] = (uint)~(self.data[i]); } @@ -290,7 +290,7 @@ fn BigInt* BigInt.sub_this(&self, BigInt other) @operator(-=) // roll over to negative if (carry_in != 0) { - for (uint i = self.len; i < MAX_LEN; i++) + for (int i = self.len; i < MAX_LEN; i++) { self.data[i] = 0xFFFFFFFF; } @@ -463,7 +463,7 @@ macro bool BigInt.greater_than(&self, BigInt other) for (pos = len - 1; pos >= 0 && self.data[pos] == other.data[pos]; pos--); return pos >= 0 && self.data[pos] > other.data[pos]; } -macro bool BigInt.less_than(&self, BigInt other) +macro bool BigInt.less_than(&self, BigInt other) @operator(<) { if (self.is_negative() && !other.is_negative()) return true; if (!self.is_negative() && other.is_negative()) return false; @@ -492,7 +492,7 @@ macro bool BigInt.greater_or_equal(&self, BigInt other) return self.greater_than(other) || self.equals(other); } -macro bool BigInt.less_or_equal(&self, BigInt) +macro bool BigInt.less_or_equal(&self, BigInt other) { return self.less_than(other) || self.equals(other); } @@ -507,7 +507,7 @@ fn String BigInt.to_string(&self, Allocator allocator) @dynamic return self.to_string_with_radix(10, allocator); } -fn usz? BigInt.to_format(&self, Formatter* format) @dynamic +fn sz? BigInt.to_format(&self, Formatter* format) @dynamic { if (self.is_zero()) { @@ -516,7 +516,7 @@ fn usz? BigInt.to_format(&self, Formatter* format) @dynamic } BigInt a = *self; bool negative = self.is_negative(); - usz len; + sz len; if (negative) { format.print_char('-')!; @@ -526,8 +526,8 @@ fn usz? BigInt.to_format(&self, Formatter* format) @dynamic uint[280] chunks @noinit; int chunk_count; - const BASE10_9 = 1000000000; - foreach_r(d : self.data[:self.len]) + const ulong BASE10_9 = 1000000000; + foreach_r (d : self.data[:self.len]) { ulong carry = d; for (int i = 0; i < chunk_count; i++) @@ -584,7 +584,7 @@ fn String BigInt.to_string_with_radix(&self, int radix, Allocator allocator) } else { - str.append(CHARS[remainder.data[0] - 10]); + str.append(CHARS[remainder.data[0] - 10u]); } a = quotient; } @@ -618,7 +618,7 @@ fn BigInt BigInt.mod_pow(&self, BigInt exp, BigInt mod) // calculate constant = b^(2k) / m BigInt constant = ZERO; - uint i = mod.len << 1; + int i = mod.len << 1; constant.data[i] = 0x00000001; constant.len = i + 1; @@ -757,11 +757,11 @@ fn BigInt barrett_reduction(BigInt x, BigInt n, BigInt constant) fn BigInt BigInt.sqrt(&self) { - uint num_bits = self.bitcount(); + int num_bits = self.bitcount(); num_bits = num_bits & 0x1 != 0 ? num_bits >> 1 + 1 : num_bits >> 1; - uint byte_pos = num_bits >> 5; + int byte_pos = num_bits >> 5; int bit_pos = num_bits & 0x1F; uint mask; @@ -805,7 +805,7 @@ fn BigInt BigInt.bit_and(self, BigInt bi2) @operator(&) fn void BigInt.bit_and_this(&self, BigInt bi2) { - uint len = max(self.len, bi2.len); + int len = max(self.len, bi2.len); foreach (i, &ref : self.data[:len]) { *ref = *ref & bi2.data[i]; @@ -823,7 +823,7 @@ fn BigInt BigInt.bit_or(self, BigInt bi2) @operator(|) fn void BigInt.bit_or_this(&self, BigInt bi2) { - uint len = max(self.len, bi2.len); + int len = max(self.len, bi2.len); foreach (i, &ref : self.data[:len]) { *ref = *ref | bi2.data[i]; @@ -841,7 +841,7 @@ fn BigInt BigInt.bit_xor(self, BigInt bi2) @operator(^) fn void BigInt.bit_xor_this(&self, BigInt bi2) { - uint len = max(self.len, bi2.len); + int len = max(self.len, bi2.len); foreach (i, &ref : self.data[:len]) { *ref = *ref ^ bi2.data[i]; @@ -971,7 +971,7 @@ fn void single_byte_divide(BigInt* self, BigInt* bi2, BigInt* quotient, BigInt* } quotient.len = result_pos; - uint j = 0; + int j = 0; for (int i = result_pos - 1; i >= 0; i--, j++) { quotient.data[j] = result[i]; diff --git a/test/stdlib/src/math/complex.c3 b/test/stdlib/src/math/complex.c3 index f03aaea..c7f5299 100644 --- a/test/stdlib/src/math/complex.c3 +++ b/test/stdlib/src/math/complex.c3 @@ -6,15 +6,14 @@ alias Complexf = ComplexNumber {float}; alias Complex = ComplexNumber {double}; alias COMPLEX_IDENTITY @builtin = complex::IDENTITY {double}; alias COMPLEXF_IDENTITY @builtin = complex::IDENTITY {float}; -alias IMAGINARY @builtin @deprecated("Use I") = complex::IMAGINARY { double }; -alias IMAGINARYF @builtin @deprecated("Use I_F") = complex::IMAGINARY { float }; + alias I @builtin = complex::IMAGINARY { double }; alias I_F @builtin = complex::IMAGINARY { float }; <* The generic complex number module, for float or double based complex number definitions. - @require Real.kindof == FLOAT : "A complex number must use a floating type" + @require Real::kind == FLOAT : "A complex number must use a floating type" *> module std::math::complex ; import std::io; @@ -58,9 +57,8 @@ macro ComplexNumber ComplexNumber.conjugate(self) => { .r = self.r, .c = -self.c macro ComplexNumber ComplexNumber.negate(self) @operator(-) => { .v = -self.v }; macro bool ComplexNumber.equals(self, ComplexNumber b) @operator(==) => self.v == b.v; macro bool ComplexNumber.equals_real(self, Real r) @operator_s(==) => self.v == { r, 0 }; -macro bool ComplexNumber.not_equals(self, ComplexNumber b) @operator(!=) => self.v != b.v; -fn usz? ComplexNumber.to_format(&self, Formatter* f) @dynamic +fn sz? ComplexNumber.to_format(&self, Formatter* f) @dynamic { return f.printf("%g%+gi", self.r, self.c); } \ No newline at end of file diff --git a/test/stdlib/src/math/distributions.c3 b/test/stdlib/src/math/distributions.c3 index 62963f0..e389ac4 100644 --- a/test/stdlib/src/math/distributions.c3 +++ b/test/stdlib/src/math/distributions.c3 @@ -238,14 +238,14 @@ fn TDist t_distribution(double df) fn double TDist.mean(&self) @dynamic { - if (self.df <= 1.0) return double.nan; + if (self.df <= 1.0) return double::nan; return 0.0; } fn double TDist.variance(&self) @dynamic { - if (self.df <= 1.0) return double.nan; - if (self.df <= 2.0) return double.inf; + if (self.df <= 1.0) return double::nan; + if (self.df <= 2.0) return double::inf; return self.df / (self.df - 2.0); } @@ -280,7 +280,7 @@ fn double TDist.quantile(&self, double p) @dynamic { if (p == 0.5) return 0.0; double x = (p < 0.5) ? -1.0 : 1.0; - return newton_raphson(self, x, p) ?? double.nan; + return newton_raphson(self, x, p) ?? double::nan; } fn double TDist.random(&self, Random rand) @dynamic @@ -311,13 +311,13 @@ fn FDist f_distribution(double d1, double d2) fn double FDist.mean(&self) @dynamic { - if (self.d2 <= 2.0) return double.nan; + if (self.d2 <= 2.0) return double::nan; return self.d2 / (self.d2 - 2.0); } fn double FDist.variance(&self) @dynamic { - if (self.d2 <= 4.0) return double.nan; + if (self.d2 <= 4.0) return double::nan; double d1 = self.d1; double d2 = self.d2; return 2.0 * d2 * d2 * (d1 + d2 - 2.0) / @@ -396,7 +396,7 @@ fn double ChiSquaredDist.variance(&self) @dynamic fn double ChiSquaredDist.pdf(&self, double x) @dynamic { if (x < 0.0) return 0.0; - if (x == 0.0 && self.k < 2.0) return double.inf; + if (x == 0.0 && self.k < 2.0) return double::inf; if (x == 0.0) return 0.0; double k = self.k; diff --git a/test/stdlib/src/math/distributions_private.c3 b/test/stdlib/src/math/distributions_private.c3 index 904ab7d..e8bfe87 100644 --- a/test/stdlib/src/math/distributions_private.c3 +++ b/test/stdlib/src/math/distributions_private.c3 @@ -5,7 +5,7 @@ import std::math::random; struct ConvergenceControl { - usz max_iter; + sz max_iter; double epsilon; } @@ -39,7 +39,7 @@ fn double binomial_coefficient(int n, int k) *> fn double ln_factorial(int n) { - if (n < 0) return double.nan; + if (n < 0) return double::nan; if (n <= 1) return 0.0; return math::lgamma((double)(n + 1)); } @@ -60,7 +60,7 @@ fn double beta_function(double a, double b) *> fn double incomplete_beta(double x, double a, double b, ConvergenceControl conv = DEFAULT_CONV) { - if (x < 0.0 || x > 1.0) return double.nan; + if (x < 0.0 || x > 1.0) return double::nan; if (x == 0.0) return 0.0; if (x == 1.0) return 1.0; @@ -77,8 +77,8 @@ fn double incomplete_beta(double x, double a, double b, ConvergenceControl conv double c = 1.0; double d = 0.0; - usz m; - for (usz i = 0; i <= conv.max_iter; ++i) + sz m; + for (sz i = 0; i <= conv.max_iter; ++i) { m = i/2; @@ -110,7 +110,7 @@ fn double incomplete_beta(double x, double a, double b, ConvergenceControl conv if (math::abs(1.0 - cd) < conv.epsilon) return front * (f - 1.0); } - return double.nan; // Not convered. + return double::nan; // Not convered. } <* @@ -124,7 +124,7 @@ fn double? bisection_search(ContinuousDistribution dist, double low, double high while (dist.cdf(high) < p) high *= 2.0; // Bisection search. - for (usz i = 0; i < conv.max_iter; i++) + for (sz i = 0; i < conv.max_iter; i++) { double mid = (low + high) * 0.5; if (high - low < conv.epsilon) return mid; @@ -149,7 +149,7 @@ fn double? newton_raphson(ContinuousDistribution dist, double x, double p, ConvergenceControl conv = DEFAULT_CONV) { double delta, pdf; - for (usz i = 0; i < conv.max_iter; i++) + for (sz i = 0; i < conv.max_iter; i++) { pdf = dist.pdf(x); if (pdf < 1e-300) break; @@ -210,15 +210,15 @@ fn double inverse_erf(double x) { if (x < -1 || x > 1) { - return double.nan; + return double::nan; } else if (x == 1.0) { - return double.inf; + return double::inf; } else if (x == -1.0) { - return -double.inf; + return -double::inf; } const double LN2 = 6.931471805599453094172321214581e-1; @@ -324,16 +324,16 @@ fn double inverse_erf(double x) fn double lower_incomplete_gamma(double s, double x) { if (x == 0.0) return 0.0; - if (x == double.inf) return 1.0; + if (x == double::inf) return 1.0; // Use series expansion for x < s+1 if (x < s + 1.0) { - return incomplete_gamma_series_expansion(s, x) ?? double.nan; + return incomplete_gamma_series_expansion(s, x) ?? double::nan; } else { - return 1.0 - incomplete_gamma_continued_fraction(s, x) ?? double.nan; + return 1.0 - incomplete_gamma_continued_fraction(s, x) ?? double::nan; } } diff --git a/test/stdlib/src/math/easing.c3 b/test/stdlib/src/math/easing.c3 index c91ddbe..f2087e3 100644 --- a/test/stdlib/src/math/easing.c3 +++ b/test/stdlib/src/math/easing.c3 @@ -42,8 +42,8 @@ fn float linear_out(float t, float b, float c, float d) @inline => c * t / d + b fn float linear_inout(float t, float b, float c, float d) @inline => c * t / d + b; // Sine Easing functions -fn float sine_in(float t, float b, float c, float d) @inline => -c * math::cos(t / d * (float)math::PI_2) + c + b; -fn float sine_out(float t, float b, float c, float d) @inline => c * math::sin(t / d * (float)math::PI_2) + b; +fn float sine_in(float t, float b, float c, float d) @inline => -c * math::cos(t / d * (float)math::HALF_PI) + c + b; +fn float sine_out(float t, float b, float c, float d) @inline => c * math::sin(t / d * (float)math::HALF_PI) + b; fn float sine_inout(float t, float b, float c, float d) @inline => (-c / 2) * (math::cos((float)math::PI * t / d) - 1) + b; // Circular Easing functions diff --git a/test/stdlib/src/math/math.c3 b/test/stdlib/src/math/math.c3 index 8bbd7ff..0af8858 100644 --- a/test/stdlib/src/math/math.c3 +++ b/test/stdlib/src/math/math.c3 @@ -14,13 +14,13 @@ const LOG10E = 0.434294481903251827651128918916605082; // log10(e) const LN2 = 0.693147180559945309417232121458176568; // ln(2) const LN10 = 2.30258509299404568401799145468436421; // ln(10) const PI = 3.14159265358979323846264338327950288419716939937510; // pi -const PI_2 = 1.57079632679489661923132169163975144; // pi / 2 -const PI_4 = 0.785398163397448309615660845819875721; // pi / 4 -const DIV_PI = 0.318309886183790671537767526745028724; // 1 / pi -const DIV_2_PI = 0.636619772367581343075535053490057448; // 2 / pi -const DIV_2_SQRTPI = 1.12837916709551257389615890312154517; // 2/sqrt(pi) +const HALF_PI = 1.57079632679489661923132169163975144; // pi / 2 +const QUARTER_PI = 0.785398163397448309615660845819875721; // pi / 4 +const INV_PI = 0.318309886183790671537767526745028724; // 1 / pi +const TWO_OVER_PI = 0.636619772367581343075535053490057448; // 2 / pi +const TWO_OVER_SQRT_PI = 1.12837916709551257389615890312154517; // 2/sqrt(pi) const SQRT2 = 1.41421356237309504880168872420969808; // sqrt(2) -const double DIV_1_SQRT2 = 0.707106781186547524400844362104849039; // 1 / sqrt(2) +const double INV_SQRT2 = 0.707106781186547524400844362104849039; // 1 / sqrt(2) const HALF_MAX = 6.5504e+4; const HALF_MIN = 6.103515625e-5; @@ -45,6 +45,7 @@ const FLOAT_MIN_10_EXP = -37; const FLOAT_MAX_EXP = 128; const FLOAT_MIN_EXP = -125; const FLOAT_EPSILON = 1.1920928955078125e-07; +const FLOAT_VECTOR_EPSILON = 2e-6; const DOUBLE_MAX = 1.79769313486231570815e+308; const DOUBLE_MIN = 2.2250738585072014e-308; @@ -57,6 +58,7 @@ const DOUBLE_MIN_10_EXP = -307; const DOUBLE_MAX_EXP = 1024; const DOUBLE_MIN_EXP = -1021; const DOUBLE_EPSILON = 2.22044604925031308085e-16; +const DOUBLE_VECTOR_EPSILON = 2e-12; enum RoundingMode : int { @@ -66,23 +68,36 @@ enum RoundingMode : int TOWARD_NEG_INFINITY } -faultdef OVERFLOW, MATRIX_INVERSE_DOESNT_EXIST; +faultdef OVERFLOW, MATRIX_INVERSE_DOESNT_EXIST, NO_INTERSECTION; <* - @require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector` + @require values::@is_promotable_to_floatlike(degrees) : `The input must be a numerical value or numerical vector` *> -macro deg_to_rad(x) => x * PI / 180; +macro deg_to_rad(degrees) +{ + var val = values::promote_int(degrees); + return val * ($typeof(val))(PI / 180); +} <* - @require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector` + @require values::@is_promotable_to_floatlike(radians) : `The input must be a numerical value or numerical vector` *> -macro rad_to_deg(x) => x * 180 / PI; +macro rad_to_deg(radians) +{ + var val = values::promote_int(radians); + return val * ($typeof(val))(180 / PI); +} <* @require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector` *> macro abs(x) => $$abs(x); +<* + @require $defined($x < 0, -$x) : `The input must be a numerical value which can be negated` +*> +macro @abs($x) => $x >= 0 ??? $x : -$x; + <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" @require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value" @@ -112,12 +127,12 @@ macro bool is_approx_rel(x, y, eps) } <* - @require $kindof(x).is_int() : `The input must be an integer` + @require @kindof(x).is_int() : `The input must be an integer` *> macro sign(x) { var $Type = $typeof(x); - $if $Type.kindof == UNSIGNED_INT: + $if $Type::kind == UNSIGNED_INT: return ($Type)(x > 0); $else return ($Type)(x > 0) - ($Type)(x < 0); @@ -175,7 +190,7 @@ macro atan2(x, y) <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" - @require $kindof(sinp) == POINTER : "Expected sinp to be a pointer" + @require @kindof(sinp) == POINTER : "Expected sinp to be a pointer" @require @typematch(sinp, cosp) : "Expected sinp and cosp to have the same type" @require $defined(*sinp = x) : "Expected x and *sinp/*cosp to have the same type" *> @@ -206,18 +221,6 @@ macro sincos(x) return v; } -<* - @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" -*> -macro atan(x) -{ - $if $typeof(x) == float: - return _atanf(x); - $else - return _atan(x); - $endif -} - <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" *> @@ -233,14 +236,7 @@ macro atanh(x) <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" *> -macro acos(x) -{ - $if $typeof(x) == float: - return _acosf(x); - $else - return _acos(x); - $endif -} +macro acos(x) => $$acos(values::promote_int(x)); <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" @@ -257,14 +253,7 @@ macro acosh(x) <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" *> -macro asin(x) -{ - $if $typeof(x) == float: - return _asinf(x); - $else - return _asin(x); - $endif -} +macro asin(x) => $$asin(values::promote_int(x)); <* @require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value" @@ -278,6 +267,11 @@ macro asinh(x) $endif } +<* + @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` +*> +macro atan(x) => $$atan(values::promote_int(x)); + <* @require values::@is_floatlike(x) : `The input must be a floating point value or float vector` *> @@ -286,7 +280,7 @@ macro ceil(x) => $$ceil(x); <* Ceil for compile time evaluation. - @require $kindof($input) == FLOAT : "Only float and double may be used" + @require @kindof($input) == FLOAT : "Only float and double may be used" *> macro @ceil($input) @const => $$ceil($input); @@ -316,29 +310,31 @@ macro copysign(mag, sgn) => $$copysign(values::promote_int_same(mag, sgn), ($typ macro cos(x) => $$cos(values::promote_int(x)); <* + Cosecant + @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> -macro cosec(x) => 1 / sin(x); +macro csc(x) => 1 / sin(x); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> -macro cosech(x) => 2 / (exp(x) - exp(-x)); +macro csch(x) => 2 / (exp(x) - exp(-x)); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> -macro cosh(x) => (exp(x) + exp(-x)) / 2.0; +macro cosh(x) => $$cosh(values::promote_int(x)); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> -macro cotan(x) => cos(x) / sin(x); +macro cot(x) => cos(x) / sin(x); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> -macro cotanh(x) => (exp(2.0 * x) + 1.0) / (exp(2.0 * x) - 1.0); +macro coth(x) => (exp(2.0 * x) + 1.0) / (exp(2.0 * x) - 1.0); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` @@ -350,12 +346,19 @@ macro exp(x) => $$exp(values::promote_int(x)); *> macro exp2(x) => $$exp2(values::promote_int(x)); +<* + @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` +*> +macro exp10(x) => $$exp10(values::promote_int(x)); + <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number value or float vector` *> macro floor(x) => $$floor(values::promote_int(x)); <* + Fused multiply-add. Also consider mad if looser guarantees on fusing the operation is desired. + @require values::@is_promotable_to_floatlike(a) : `The input must be a number or float vector` @require values::@is_promotable_to_floatlike(b) : `The input must be a number or float vector` @require values::@is_promotable_to_floatlike(c) : `The input must be a number or float vector` @@ -448,10 +451,12 @@ macro min(x, y, ...) } <* + Multiply and add - possibly fused + @require types::@is_float(a) : `The input must be a floating point value` @require types::@has_same(a, b, c) : `The input types must be equal` *> -macro muladd(a, b, c) => $$fmuladd(a, b, c); +macro mad(a, b, c) => $$fmuladd(a, b, c); <* @require values::@is_floatlike(x) : `The input must be a floating point value or float vector` @@ -467,7 +472,7 @@ macro pow(x, exp) $if types::is_floatlike($typeof(exp)): return $$pow(values::promote_int_same(x, exp), ($typeof(values::promote_int_same(x, exp)))exp); $else - return $$pow_int(values::promote_int(x), exp); + return $$pow_int(values::promote_int(x), (int)exp); $endif } @@ -493,7 +498,7 @@ macro int signbit(x) $switch $typeof(x): $case float: $case float16: - return bitcast((float)x, uint) >> 31; + return (int)(bitcast((float)x, uint) >> 31); $default: return (int)(bitcast((double)x, ulong) >> 63); $endswitch @@ -542,7 +547,7 @@ macro sin(x) => $$sin(values::promote_int(x)); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` *> -macro sinh(x) => (exp(x) - exp(-x)) / 2.0; +macro sinh(x) => $$sinh(values::promote_int(x)); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` @@ -557,18 +562,12 @@ macro sqrt(x) => $$sqrt(values::promote_int(x)); <* @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` *> -macro tan(x) -{ - var $Type = $typeof(x); - $switch: - $case types::is_vector($Type): - return $$sin(x) / $$cos(x); - $case $Type.typeid == float.typeid: - return _tanf(x); - $default: - return _tan(x); - $endswitch -} +macro tan(x) => $$tan(values::promote_int(x)); + +<* + @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` +*> +macro tanh(x) => $$tanh(values::promote_int(x)); <* @require values::@is_promotable_to_float(x) : `The input must be a float` @@ -612,29 +611,11 @@ macro bool is_inf(x) $endswitch } -<* - @require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector` -*> -macro tanh(x) => (exp(2.0 * x) - 1.0) / (exp(2.0 * x) + 1.0); - <* @require values::@is_floatlike(x) : `The input must be a floating point value or float vector` *> macro trunc(x) => $$trunc(x); -macro lerp(x, y, amount) @private => x + (y - x) * amount; -macro reflect(x, y) @private -{ - var dot = x.dot(y); - return x - 2 * y * dot; -} -macro normalize(x) @private -{ - var len = x.length(); - if (len == 0) return x; - return x * (1 / len); -} - <* Use a mask to select values from either "then" or "else" vectors. @@ -657,259 +638,30 @@ macro float float.clamp(float x, float lower, float upper) => $$max(lower, $$min macro float float.copysign(float mag, float sgn) => $$copysign(mag, sgn); macro float float.floor(float x) => $$floor(x); macro float float.fma(float a, float b, float c) => $$fma(a, b, c); -macro float float.muladd(float a, float b, float c) => $$fmuladd(a, b, c); +macro float float.mad(float a, float b, float c) => $$fmuladd(a, b, c); macro float float.nearbyint(float x) => $$nearbyint(x); macro float float.pow(float x, exp) => pow(x, exp); macro float float.rint(float x) => $$rint(x); macro float float.round(float x) => $$round(x); macro float float.roundeven(float x) => $$roundeven(x); macro float float.trunc(float x) => $$trunc(x); - -macro float float[<*>].sum(float[<*>] x, float start = 0.0) => $$reduce_fadd(x, start); -macro float float[<*>].product(float[<*>] x, float start = 1.0) => $$reduce_fmul(x, start); -macro float float[<*>].max(float[<*>] x) => $$reduce_max(x); -macro float float[<*>].min(float[<*>] x) => $$reduce_min(x); -macro float[<*>] float[<*>].ceil(float[<*>] x) => $$ceil(x); -macro float[<*>] float[<*>].clamp(float[<*>] x, float[<*>] lower, float[<*>] upper) => $$max(lower, $$min(x, upper)); -macro float[<*>] float[<*>].copysign(float[<*>] mag, float[<*>] sgn) => $$copysign(mag, sgn); -macro float[<*>] float[<*>].fma(float[<*>] a, float[<*>] b, float[<*>] c) => $$fma(a, b, c); -macro float[<*>] float[<*>].floor(float[<*>] x) => $$floor(x); -macro float[<*>] float[<*>].nearbyint(float[<*>] x) => $$nearbyint(x); -macro float[<*>] float[<*>].pow(float[<*>] x, exp) => pow(x, exp); -macro float[<*>] float[<*>].rint(float[<*>] x) => $$rint(x); -macro float[<*>] float[<*>].round(float[<*>] x) => $$round(x); -macro float[<*>] float[<*>].roundeven(float[<*>] x) => $$roundeven(x); -macro float[<*>] float[<*>].trunc(float[<*>] x) => $$trunc(x); -macro float float[<*>].dot(float[<*>] x, float[<*>] y) => (x * y).sum(); -macro float float[<*>].length(float[<*>] x) => $$sqrt(x.dot(x)); -macro float float[<*>].distance(float[<*>] x, float[<*>] y) => (x - y).length(); -macro float[<*>] float[<*>].normalize(float[<*>] x) => normalize(x); -macro float[<*>] float[<*>].lerp(float[<*>] x, float[<*>] y, float amount) => lerp(x, y, amount); -macro float[<*>] float[<*>].reflect(float[<*>] x, float[<*>] y) => reflect(x, y); -macro bool float[<*>].equals(float[<*>] x, float[<*>] y) => equals_vec(x, y); - -macro bool[<*>] float[<*>].comp_lt(float[<*>] x, float[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] float[<*>].comp_le(float[<*>] x, float[<*>] y) => $$veccomple(x, y); -macro bool[<*>] float[<*>].comp_eq(float[<*>] x, float[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] float[<*>].comp_gt(float[<*>] x, float[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] float[<*>].comp_ge(float[<*>] x, float[<*>] y) => $$veccompge(x, y); -macro bool[<*>] float[<*>].comp_ne(float[<*>] x, float[<*>] y) => $$veccompne(x, y); +macro float float.to_degrees(float radians) => radians * (float)(180 / PI); +macro float float.to_radians(float degrees) => degrees * (float)(PI / 180); macro double double.ceil(double x) => $$ceil(x); macro double double.clamp(double x, double lower, double upper) => $$max(lower, $$min(x, upper)); macro double double.copysign(double mag, double sgn) => $$copysign(mag, sgn); macro double double.floor(double x) => $$floor(x); macro double double.fma(double a, double b, double c) => $$fma(a, b, c); -macro double double.muladd(double a, double b, double c) => $$fmuladd(a, b, c); +macro double double.mad(double a, double b, double c) => $$fmuladd(a, b, c); macro double double.nearbyint(double x) => $$nearbyint(x); macro double double.pow(double x, exp) => pow(x, exp); macro double double.rint(double x) => $$rint(x); macro double double.round(double x) => $$round(x); macro double double.roundeven(double x) => $$roundeven(x); macro double double.trunc(double x) => $$trunc(x); - -macro double double[<*>].sum(double[<*>] x, double start = 0.0) => $$reduce_fadd(x, start); -macro double double[<*>].product(double[<*>] x, double start = 1.0) => $$reduce_fmul(x, start); -macro double double[<*>].max(double[<*>] x) => $$reduce_fmax(x); -macro double double[<*>].min(double[<*>] x) => $$reduce_fmin(x); -macro double[<*>] double[<*>].ceil(double[<*>] x) => $$ceil(x); -macro double[<*>] double[<*>].clamp(double[<*>] x, double[<*>] lower, double[<*>] upper) => $$max(lower, $$min(x, upper)); -macro double[<*>] double[<*>].copysign(double[<*>] mag, double[<*>] sgn) => $$copysign(mag, sgn); -macro double[<*>] double[<*>].floor(double[<*>] x) => $$floor(x); -macro double[<*>] double[<*>].fma(double[<*>] a, double[<*>] b, double[<*>] c) => $$fma(a, b, c); -macro double[<*>] double[<*>].nearbyint(double[<*>] x) => $$nearbyint(x); -macro double[<*>] double[<*>].pow(double[<*>] x, exp) => pow(x, exp); -macro double[<*>] double[<*>].rint(double[<*>] x) => $$rint(x); -macro double[<*>] double[<*>].round(double[<*>] x) => $$round(x); -macro double[<*>] double[<*>].roundeven(double[<*>] x) => $$roundeven(x); -macro double[<*>] double[<*>].trunc(double[<*>] x) => $$trunc(x); -macro double double[<*>].dot(double[<*>] x, double[<*>] y) => (x * y).sum(); -macro double double[<*>].length(double[<*>] x) => $$sqrt(x.dot(x)); -macro double double[<*>].distance(double[<*>] x, double[<*>] y) => (x - y).length(); -macro double[<*>] double[<*>].normalize(double[<*>] x) => normalize(x); -macro double[<*>] double[<*>].reflect(double[<*>] x, double[<*>] y) => reflect(x, y); -macro double[<*>] double[<*>].lerp(double[<*>] x, double[<*>] y, double amount) => lerp(x, y, amount); -macro bool double[<*>].equals(double[<*>] x, double[<*>] y) => equals_vec(x, y); - -macro bool[<*>] double[<*>].comp_lt(double[<*>] x, double[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] double[<*>].comp_le(double[<*>] x, double[<*>] y) => $$veccomple(x, y); -macro bool[<*>] double[<*>].comp_eq(double[<*>] x, double[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] double[<*>].comp_gt(double[<*>] x, double[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] double[<*>].comp_ge(double[<*>] x, double[<*>] y) => $$veccompge(x, y); -macro bool[<*>] double[<*>].comp_ne(double[<*>] x, double[<*>] y) => $$veccompne(x, y); - -macro bool[<*>] ichar[<*>].comp_lt(ichar[<*>] x, ichar[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] ichar[<*>].comp_le(ichar[<*>] x, ichar[<*>] y) => $$veccomple(x, y); -macro bool[<*>] ichar[<*>].comp_eq(ichar[<*>] x, ichar[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] ichar[<*>].comp_gt(ichar[<*>] x, ichar[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] ichar[<*>].comp_ge(ichar[<*>] x, ichar[<*>] y) => $$veccompge(x, y); -macro bool[<*>] ichar[<*>].comp_ne(ichar[<*>] x, ichar[<*>] y) => $$veccompne(x, y); - -macro ichar ichar[<*>].sum(ichar[<*>] x) => $$reduce_add(x); -macro ichar ichar[<*>].product(ichar[<*>] x) => $$reduce_mul(x); -macro ichar ichar[<*>].and(ichar[<*>] x) => $$reduce_and(x); -macro ichar ichar[<*>].or(ichar[<*>] x) => $$reduce_or(x); -macro ichar ichar[<*>].xor(ichar[<*>] x) => $$reduce_xor(x); -macro ichar ichar[<*>].max(ichar[<*>] x) => $$reduce_max(x); -macro ichar ichar[<*>].min(ichar[<*>] x) => $$reduce_min(x); -macro ichar ichar[<*>].dot(ichar[<*>] x, ichar[<*>] y) => (x * y).sum(); - -macro bool[<*>] short[<*>].comp_lt(short[<*>] x, short[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] short[<*>].comp_le(short[<*>] x, short[<*>] y) => $$veccomple(x, y); -macro bool[<*>] short[<*>].comp_eq(short[<*>] x, short[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] short[<*>].comp_gt(short[<*>] x, short[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] short[<*>].comp_ge(short[<*>] x, short[<*>] y) => $$veccompge(x, y); -macro bool[<*>] short[<*>].comp_ne(short[<*>] x, short[<*>] y) => $$veccompne(x, y); - -macro short short[<*>].sum(short[<*>] x) => $$reduce_add(x); -macro short short[<*>].product(short[<*>] x) => $$reduce_mul(x); -macro short short[<*>].and(short[<*>] x) => $$reduce_and(x); -macro short short[<*>].or(short[<*>] x) => $$reduce_or(x); -macro short short[<*>].xor(short[<*>] x) => $$reduce_xor(x); -macro short short[<*>].max(short[<*>] x) => $$reduce_max(x); -macro short short[<*>].min(short[<*>] x) => $$reduce_min(x); -macro short short[<*>].dot(short[<*>] x, short[<*>] y) => (x * y).sum(); - -macro bool[<*>] int[<*>].comp_lt(int[<*>] x, int[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] int[<*>].comp_le(int[<*>] x, int[<*>] y) => $$veccomple(x, y); -macro bool[<*>] int[<*>].comp_eq(int[<*>] x, int[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] int[<*>].comp_gt(int[<*>] x, int[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] int[<*>].comp_ge(int[<*>] x, int[<*>] y) => $$veccompge(x, y); -macro bool[<*>] int[<*>].comp_ne(int[<*>] x, int[<*>] y) => $$veccompne(x, y); - -macro int int[<*>].sum(int[<*>] x) => $$reduce_add(x); -macro int int[<*>].product(int[<*>] x) => $$reduce_mul(x); -macro int int[<*>].and(int[<*>] x) => $$reduce_and(x); -macro int int[<*>].or(int[<*>] x) => $$reduce_or(x); -macro int int[<*>].xor(int[<*>] x) => $$reduce_xor(x); -macro int int[<*>].max(int[<*>] x) => $$reduce_max(x); -macro int int[<*>].min(int[<*>] x) => $$reduce_min(x); -macro int int[<*>].dot(int[<*>] x, int[<*>] y) => (x * y).sum(); - -macro bool[<*>] long[<*>].comp_lt(long[<*>] x, long[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] long[<*>].comp_le(long[<*>] x, long[<*>] y) => $$veccomple(x, y); -macro bool[<*>] long[<*>].comp_eq(long[<*>] x, long[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] long[<*>].comp_gt(long[<*>] x, long[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] long[<*>].comp_ge(long[<*>] x, long[<*>] y) => $$veccompge(x, y); -macro bool[<*>] long[<*>].comp_ne(long[<*>] x, long[<*>] y) => $$veccompne(x, y); -macro long long[<*>].sum(long[<*>] x) => $$reduce_add(x); -macro long long[<*>].product(long[<*>] x) => $$reduce_mul(x); -macro long long[<*>].and(long[<*>] x) => $$reduce_and(x); -macro long long[<*>].or(long[<*>] x) => $$reduce_or(x); -macro long long[<*>].xor(long[<*>] x) => $$reduce_xor(x); -macro long long[<*>].max(long[<*>] x) => $$reduce_max(x); -macro long long[<*>].min(long[<*>] x) => $$reduce_min(x); -macro long long[<*>].dot(long[<*>] x, long[<*>] y) => (x * y).sum(); - -macro bool[<*>] int128[<*>].comp_lt(int128[<*>] x, int128[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] int128[<*>].comp_le(int128[<*>] x, int128[<*>] y) => $$veccomple(x, y); -macro bool[<*>] int128[<*>].comp_eq(int128[<*>] x, int128[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] int128[<*>].comp_gt(int128[<*>] x, int128[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] int128[<*>].comp_ge(int128[<*>] x, int128[<*>] y) => $$veccompge(x, y); -macro bool[<*>] int128[<*>].comp_ne(int128[<*>] x, int128[<*>] y) => $$veccompne(x, y); -macro int128 int128[<*>].sum(int128[<*>] x) => $$reduce_add(x); -macro int128 int128[<*>].product(int128[<*>] x) => $$reduce_mul(x); -macro int128 int128[<*>].and(int128[<*>] x) => $$reduce_and(x); -macro int128 int128[<*>].or(int128[<*>] x) => $$reduce_or(x); -macro int128 int128[<*>].xor(int128[<*>] x) => $$reduce_xor(x); -macro int128 int128[<*>].max(int128[<*>] x) => $$reduce_max(x); -macro int128 int128[<*>].min(int128[<*>] x) => $$reduce_min(x); -macro int128 int128[<*>].dot(int128[<*>] x, int128[<*>] y) => (x * y).sum(); - -macro bool[<*>] bool[<*>].comp_lt(bool[<*>] x, bool[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] bool[<*>].comp_le(bool[<*>] x, bool[<*>] y) => $$veccomple(x, y); -macro bool[<*>] bool[<*>].comp_eq(bool[<*>] x, bool[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] bool[<*>].comp_gt(bool[<*>] x, bool[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] bool[<*>].comp_ge(bool[<*>] x, bool[<*>] y) => $$veccompge(x, y); -macro bool[<*>] bool[<*>].comp_ne(bool[<*>] x, bool[<*>] y) => $$veccompne(x, y); - -macro bool bool[<*>].sum(bool[<*>] x) => $$reduce_add(x); -macro bool bool[<*>].product(bool[<*>] x) => $$reduce_mul(x); -macro bool bool[<*>].and(bool[<*>] x) => $$reduce_and(x); -macro bool bool[<*>].or(bool[<*>] x) => $$reduce_or(x); -macro bool bool[<*>].xor(bool[<*>] x) => $$reduce_xor(x); -macro bool bool[<*>].max(bool[<*>] x) => $$reduce_max(x); -macro bool bool[<*>].min(bool[<*>] x) => $$reduce_min(x); - -macro bool[<*>] char[<*>].comp_lt(char[<*>] x, char[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] char[<*>].comp_le(char[<*>] x, char[<*>] y) => $$veccomple(x, y); -macro bool[<*>] char[<*>].comp_eq(char[<*>] x, char[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] char[<*>].comp_gt(char[<*>] x, char[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] char[<*>].comp_ge(char[<*>] x, char[<*>] y) => $$veccompge(x, y); -macro bool[<*>] char[<*>].comp_ne(char[<*>] x, char[<*>] y) => $$veccompne(x, y); - -macro char char[<*>].sum(char[<*>] x) => $$reduce_add(x); -macro char char[<*>].product(char[<*>] x) => $$reduce_mul(x); -macro char char[<*>].and(char[<*>] x) => $$reduce_and(x); -macro char char[<*>].or(char[<*>] x) => $$reduce_or(x); -macro char char[<*>].xor(char[<*>] x) => $$reduce_xor(x); -macro char char[<*>].max(char[<*>] x) => $$reduce_max(x); -macro char char[<*>].min(char[<*>] x) => $$reduce_min(x); -macro char char[<*>].dot(char[<*>] x, char[<*>] y) => (x * y).sum(); - -macro bool[<*>] ushort[<*>].comp_lt(ushort[<*>] x, ushort[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] ushort[<*>].comp_le(ushort[<*>] x, ushort[<*>] y) => $$veccomple(x, y); -macro bool[<*>] ushort[<*>].comp_eq(ushort[<*>] x, ushort[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] ushort[<*>].comp_gt(ushort[<*>] x, ushort[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] ushort[<*>].comp_ge(ushort[<*>] x, ushort[<*>] y) => $$veccompge(x, y); -macro bool[<*>] ushort[<*>].comp_ne(ushort[<*>] x, ushort[<*>] y) => $$veccompne(x, y); - -macro ushort ushort[<*>].sum(ushort[<*>] x) => $$reduce_add(x); -macro ushort ushort[<*>].product(ushort[<*>] x) => $$reduce_mul(x); -macro ushort ushort[<*>].and(ushort[<*>] x) => $$reduce_and(x); -macro ushort ushort[<*>].or(ushort[<*>] x) => $$reduce_or(x); -macro ushort ushort[<*>].xor(ushort[<*>] x) => $$reduce_xor(x); -macro ushort ushort[<*>].max(ushort[<*>] x) => $$reduce_max(x); -macro ushort ushort[<*>].min(ushort[<*>] x) => $$reduce_min(x); -macro ushort ushort[<*>].dot(ushort[<*>] x, ushort[<*>] y) => (x * y).sum(); - -macro bool[<*>] uint[<*>].comp_lt(uint[<*>] x, uint[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] uint[<*>].comp_le(uint[<*>] x, uint[<*>] y) => $$veccomple(x, y); -macro bool[<*>] uint[<*>].comp_eq(uint[<*>] x, uint[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] uint[<*>].comp_gt(uint[<*>] x, uint[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] uint[<*>].comp_ge(uint[<*>] x, uint[<*>] y) => $$veccompge(x, y); -macro bool[<*>] uint[<*>].comp_ne(uint[<*>] x, uint[<*>] y) => $$veccompne(x, y); - -macro uint uint[<*>].sum(uint[<*>] x) => $$reduce_add(x); -macro uint uint[<*>].product(uint[<*>] x) => $$reduce_mul(x); -macro uint uint[<*>].and(uint[<*>] x) => $$reduce_and(x); -macro uint uint[<*>].or(uint[<*>] x) => $$reduce_or(x); -macro uint uint[<*>].xor(uint[<*>] x) => $$reduce_xor(x); -macro uint uint[<*>].max(uint[<*>] x) => $$reduce_max(x); -macro uint uint[<*>].min(uint[<*>] x) => $$reduce_min(x); -macro uint uint[<*>].dot(uint[<*>] x, uint[<*>] y) => (x * y).sum(); - -macro bool[<*>] ulong[<*>].comp_lt(ulong[<*>] x, ulong[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] ulong[<*>].comp_le(ulong[<*>] x, ulong[<*>] y) => $$veccomple(x, y); -macro bool[<*>] ulong[<*>].comp_eq(ulong[<*>] x, ulong[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] ulong[<*>].comp_gt(ulong[<*>] x, ulong[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] ulong[<*>].comp_ge(ulong[<*>] x, ulong[<*>] y) => $$veccompge(x, y); -macro bool[<*>] ulong[<*>].comp_ne(ulong[<*>] x, ulong[<*>] y) => $$veccompne(x, y); - -macro ulong ulong[<*>].sum(ulong[<*>] x) => $$reduce_add(x); -macro ulong ulong[<*>].product(ulong[<*>] x) => $$reduce_mul(x); -macro ulong ulong[<*>].and(ulong[<*>] x) => $$reduce_and(x); -macro ulong ulong[<*>].or(ulong[<*>] x) => $$reduce_or(x); -macro ulong ulong[<*>].xor(ulong[<*>] x) => $$reduce_xor(x); -macro ulong ulong[<*>].max(ulong[<*>] x) => $$reduce_max(x); -macro ulong ulong[<*>].min(ulong[<*>] x) => $$reduce_min(x); -macro ulong ulong[<*>].dot(ulong[<*>] x, ulong[<*>] y) => (x * y).sum(); - -macro bool[<*>] uint128[<*>].comp_lt(uint128[<*>] x, uint128[<*>] y) => $$veccomplt(x, y); -macro bool[<*>] uint128[<*>].comp_le(uint128[<*>] x, uint128[<*>] y) => $$veccomple(x, y); -macro bool[<*>] uint128[<*>].comp_eq(uint128[<*>] x, uint128[<*>] y) => $$veccompeq(x, y); -macro bool[<*>] uint128[<*>].comp_gt(uint128[<*>] x, uint128[<*>] y) => $$veccompgt(x, y); -macro bool[<*>] uint128[<*>].comp_ge(uint128[<*>] x, uint128[<*>] y) => $$veccompge(x, y); -macro bool[<*>] uint128[<*>].comp_ne(uint128[<*>] x, uint128[<*>] y) => $$veccompne(x, y); - -macro uint128 uint128[<*>].sum(uint128[<*>] x) => $$reduce_add(x); -macro uint128 uint128[<*>].product(uint128[<*>] x) => $$reduce_mul(x); -macro uint128 uint128[<*>].and(uint128[<*>] x) => $$reduce_and(x); -macro uint128 uint128[<*>].or(uint128[<*>] x) => $$reduce_or(x); -macro uint128 uint128[<*>].xor(uint128[<*>] x) => $$reduce_xor(x); -macro uint128 uint128[<*>].max(uint128[<*>] x) => $$reduce_max(x); -macro uint128 uint128[<*>].min(uint128[<*>] x) => $$reduce_min(x); -macro uint128 uint128[<*>].dot(uint128[<*>] x, uint128[<*>] y) => (x * y).sum(); +macro double double.to_degrees(double radians) => radians * (180 / PI); +macro double double.to_radians(double degrees) => degrees * (PI / 180); macro char char.sat_add(char x, char y) => $$sat_add(x, y); macro char char.sat_sub(char x, char y) => $$sat_sub(x, y); @@ -994,7 +746,7 @@ macro int128? int128.overflow_mul(int128 x, int128 y) => overflow_mul_helper(x, <* @require values::@is_int(x) : `The input must be an integer` *> -macro bool is_odd(x) => (bool)(x & 1); +macro bool is_odd(x) => (bool)(x & ($typeof(x))1); <* @require values::@is_int(x) : `The input must be an integer` @@ -1039,6 +791,15 @@ macro bool is_power_of_2(x) return x != 0 && (x & (x - 1)) == 0; } +<* + Compile time version. + @require types::is_underlying_int($typeof($x)) : `is_power_of_2 may only be used on integer types` +*> +macro bool @is_power_of_2($x) +{ + return $x != 0 && ($x & ($x - 1)) == 0; +} + <* Returns the next power of two that is greater than or equal to x. @@ -1047,33 +808,51 @@ macro bool is_power_of_2(x) *> macro next_power_of_2(x) { - if (x <= 1) return 1; - if (x == 2) return 2; + var $Type = $typeof(x); + if (x <= 1) return ($Type)1; + if (x == 2) return ($Type)2; - $typeof(x) v = x - 1; + $Type v = x - ($Type)1; v |= v >> 1; v |= v >> 2; v |= v >> 4; - $if ($sizeof(x) >= 2): v |= v >> 8; $endif; - $if ($sizeof(x) >= 4): v |= v >> 16; $endif; - $if ($sizeof(x) >= 8): v |= v >> 32; $endif; - $if ($sizeof(x) >= 16): v |= v >> 64; $endif; + $if (@sizeof(x) >= 2): v |= v >> 8; $endif; + $if (@sizeof(x) >= 4): v |= v >> 16; $endif; + $if (@sizeof(x) >= 8): v |= v >> 32; $endif; + $if (@sizeof(x) >= 16): v |= v >> 64; $endif; - return v + 1; + return v + ($Type)1; } -macro equals_vec(v1, v2) @private +<* + Compile time version. + Returns the next power of two that is greater than or equal to x. + + @require types::is_int($typeof($x)) : "Input must be an integer type" + @require $x >= 0 : "Input must be non-negative" +*> +macro @next_power_of_2($x) { - var $elements = v1.len; - var abs_diff = math::abs(v1 - v2); - var abs_v1 = math::abs(v1); - var abs_v2 = math::abs(v2); - $typeof(abs_v2) eps = 1; - return abs_diff.comp_le(FLOAT_EPSILON * math::max(abs_v1, abs_v2, eps)).and(); + $if $x <= 1: return 1; $endif + $if $x == 2: return 2; $endif + + $typeof($x) $v = $x - 1; + + $v |= $v >> 1; + $v |= $v >> 2; + $v |= $v >> 4; + + $if (@sizeof($x) >= 2): $v |= $v >> 8; $endif; + $if (@sizeof($x) >= 4): $v |= $v >> 16; $endif; + $if (@sizeof($x) >= 8): $v |= $v >> 32; $endif; + $if (@sizeof($x) >= 16): $v |= $v >> 64; $endif; + + return $v + 1; } + macro uint double.high_word(double d) => (uint)(bitcast(d, ulong) >> 32); macro uint double.low_word(double d) => (uint)bitcast(d, ulong); macro uint float.word(float d) => bitcast(d, uint); @@ -1161,7 +940,7 @@ fn double _frexp(double x, int* e) fn float _frexpf(float x, int* e) { uint i = bitcast(x, uint); - int ee = (i >> 23) & 0xff; + int ee = (int)(i >> 23) & 0xff; switch { @@ -1233,22 +1012,17 @@ macro bool overflow_sub(a, b, out) => $$overflow_sub(a, b, out); macro overflow_mul(a, b, out) => $$overflow_mul(a, b, out); <* - @require types::is_vector($Type) || ($Type.kindof == ARRAY &&& types::is_numerical($typefrom($Type.inner))) + @require types::is_vector($Type) || ($Type::kind == ARRAY &&& types::is_numerical($typefrom($Type::inner))) *> macro iota($Type) { $Type $val = {}; - $for var $i = 0; $i < $Type.len; $i++: + $for var $i = 0; $i < $Type::len; $i++: $val[$i] = $i; $endfor return $val; } -macro mul_div_helper(val, mul, div) @private -{ - var $Type = $typeof(val); - return ($Type)(($Type)mul * (val / ($Type)div) + ($Type)mul * (val % ($Type)div) / ($Type)div); -} macro char char.muldiv(self, char mul, char div) => mul_div_helper(self, mul, div); macro ichar ichar.muldiv(self, ichar mul, ichar div) => mul_div_helper(self, mul, div); macro short short.muldiv(self, short mul, short div) => mul_div_helper(self, mul, div); @@ -1258,81 +1032,6 @@ macro uint uint.muldiv(self, uint mul, uint div) => mul_div_helper(self, mul, di macro long long.muldiv(self, long mul, long div) => mul_div_helper(self, mul, div); macro ulong ulong.muldiv(self, ulong mul, ulong div) => mul_div_helper(self, mul, div); -macro bool @is_same_vector_or_scalar(#vector_value, #vector_or_scalar) @private -{ - return (values::@is_vector(#vector_or_scalar) &&& values::@is_same_vector_type(#vector_value, #vector_or_scalar)) ||| values::@is_int(#vector_or_scalar); -} - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro char[<*>] char[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro ichar[<*>] ichar[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro short[<*>] short[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro ushort[<*>] ushort[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro int[<*>] int[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro uint[<*>] uint[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro long[<*>] long[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` - @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` -*> -macro ulong[<*>] ulong[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div); - -<* - @require types::is_int($typeof(a)) : `The input must be an integer` - @require types::is_int($typeof(b)) : `The input must be an integer` -*> -macro _gcd(a, b) @private -{ - if (a == 0) return b; - if (b == 0) return a; - - var $Type = $typeof(a); - $Type r, aa, ab; - aa = abs(a); - ab = abs(b); - while (ab != 0) - { - r = aa % ab; - aa = ab; - ab = r; - } - return aa; -} - <* Calculate the least common multiple for the provided arguments. @@ -1368,3 +1067,36 @@ macro gcd(...) $endfor return result; } + +module std::math @private; + +<* + @require types::is_int($typeof(a)) : `The input must be an integer` + @require types::is_int($typeof(b)) : `The input must be an integer` +*> +macro _gcd(a, b) +{ + if (a == 0) return b; + if (b == 0) return a; + + var $Type = $typeof(a); + $Type r, aa, ab; + aa = abs(a); + ab = abs(b); + while (ab != 0) + { + r = aa % ab; + aa = ab; + ab = r; + } + return aa; +} + +macro lerp(x, y, amount) => x + (y - x) * amount; + + +macro mul_div_helper(val, mul, div) +{ + var $Type = $typeof(val); + return ($Type)(($Type)mul * (val / ($Type)div) + ($Type)mul * (val % ($Type)div) / ($Type)div); +} diff --git a/test/stdlib/src/math/matrix.c3 b/test/stdlib/src/math/matrix.c3 index 0d2614c..ceb1370 100644 --- a/test/stdlib/src/math/matrix.c3 +++ b/test/stdlib/src/math/matrix.c3 @@ -8,312 +8,309 @@ alias Matrix3 = Matrix3x3 {double}; alias Matrix4f = Matrix4x4 {float}; alias Matrix4 = Matrix4x4 {double}; -// Predefined matrix functions -alias matrix4_ortho @builtin = matrix::ortho {double}; -alias matrix4f_ortho @builtin = matrix::ortho {float}; -alias matrix4_perspective @builtin = matrix::perspective {double}; -alias matrix4f_perspective @builtin = matrix::perspective {float}; -alias matrix4_look_at @builtin = matrix::look_at {double}; -alias matrix4f_look_at @builtin = matrix::look_at {float}; - -alias MATRIX2_IDENTITY @builtin = matrix::IDENTITY2 {double}; -alias MATRIX2F_IDENTITY @builtin = matrix::IDENTITY2 {float}; -alias MATRIX3_IDENTITY @builtin = matrix::IDENTITY3 {double}; -alias MATRIX3F_IDENTITY @builtin = matrix::IDENTITY3 {float}; -alias MATRIX4_IDENTITY @builtin = matrix::IDENTITY4 {double}; -alias MATRIX4F_IDENTITY @builtin = matrix::IDENTITY4 {float}; +alias Mat2 = Matrix2x2 {float}; +alias Mat3 = Matrix3x3 {float}; +alias Mat4 = Matrix4x4 {float}; + <* The generic matrix module, for float or double based matrix definitions. - @require Real.kindof == FLOAT : "A matrix must use a floating type" + @require Real::kind == FLOAT : "A matrix must use a floating type" *> module std::math::matrix ; -import std::math::vector; +import std::math::vector, std::io; -struct Matrix2x2 +struct Matrix2x2 (Printable) { union { struct { - Real m00, m01; - Real m10, m11; + Real m00, m10; + Real m01, m11; } - Real[4] m; + Real[<4>] m; } } -struct Matrix3x3 +struct Matrix3x3 (Printable) { union { struct { - Real m00, m01, m02; - Real m10, m11, m12; - Real m20, m21, m22; + Real m00, m10, m20; + Real m01, m11, m21; + Real m02, m12, m22; } - Real[9] m; + Real[<9>] m; } } -struct Matrix4x4 +struct Matrix4x4 (Printable) { union { struct { - Real m00, m01, m02, m03; - Real m10, m11, m12, m13; - Real m20, m21, m22, m23; - Real m30, m31, m32, m33; + Real m00, m10, m20, m30; + Real m01, m11, m21, m31; + Real m02, m12, m22, m32; + Real m03, m13, m23, m33; } - Real[16] m; + Real[<16>] m; } } -fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec) @operator(*) +fn sz? Matrix4x4.to_format(&self, Formatter* f) @dynamic { - return { - self.m00 * vec[0] + self.m01 * vec[1], - self.m10 * vec[0] + self.m11 * vec[1], - }; + return f.printf("[%g,%g,%g,%g|%g,%g,%g,%g|%g,%g,%g,%g|%g,%g,%g,%g]", + self.m00, self.m01, self.m02, self.m03, + self.m10, self.m11, self.m12, self.m13, + self.m20, self.m21, self.m22, self.m23, + self.m30, self.m31, self.m32, self.m33); +} + +fn sz? Matrix3x3.to_format(&self, Formatter* f) @dynamic +{ + return f.printf("[%g,%g,%g|%g,%g,%g|%g,%g,%g]", + self.m00, self.m01, self.m02, + self.m10, self.m11, self.m12, + self.m20, self.m21, self.m22); +} + +fn sz? Matrix2x2.to_format(&self, Formatter* f) @dynamic +{ + return f.printf("[%g,%g|%g,%g]", + self.m00, self.m01, + self.m10, self.m11); +} + + +fn Real[<2>] Matrix2x2.apply(self, Real[<2>] vec) @operator(*) +{ + Real[<4>] m = self.m; + return m.xy * vec.xx + m.zw * vec.yy; } fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec) @operator(*) { + Real[<9>] m = self.m; return { - self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2], - self.m10 * vec[0] + self.m11 * vec[1] + self.m12 * vec[2], - self.m20 * vec[0] + self.m21 * vec[1] + self.m22 * vec[2], + m[0] * vec.x + m[3] * vec.y + m[6] * vec.z, + m[1] * vec.x + m[4] * vec.y + m[7] * vec.z, + m[2] * vec.x + m[5] * vec.y + m[8] * vec.z, }; } fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec) @operator(*) { + Real[<16>] m = self.m; return { - self.m00 * vec[0] + self.m01 * vec[1] + self.m02 * vec[2] + self.m03 * vec[3], - self.m10 * vec[0] + self.m11 * vec[1] + self.m12 * vec[2] + self.m13 * vec[3], - self.m20 * vec[0] + self.m21 * vec[1] + self.m22 * vec[2] + self.m23 * vec[3], - self.m30 * vec[0] + self.m31 * vec[1] + self.m32 * vec[2] + self.m33 * vec[3], + m[0] * vec.x + m[4] * vec.y + m[8] * vec.z + m[12] * vec.w, + m[1] * vec.x + m[5] * vec.y + m[9] * vec.z + m[13] * vec.w, + m[2] * vec.x + m[6] * vec.y + m[10] * vec.z + m[14] * vec.w, + m[3] * vec.x + m[7] * vec.y + m[11] * vec.z + m[15] * vec.w, }; } -fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b) @operator(*) +fn Matrix2x2 Matrix2x2.mul(self, Matrix2x2 b) @operator(*) { - return { - self.m00 * b.m00 + self.m01 * b.m10, self.m00 * b.m01 + self.m01 * b.m11, - self.m10 * b.m00 + self.m11 * b.m10, self.m10 * b.m01 + self.m11 * b.m11, - }; + return { .m = $$matrix_mul(self.m, b.m, 2, 2, 2) }; } fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b) @operator(*) { - return { - self.m00 * b.m00 + self.m01 * b.m10 + self.m02 * b.m20, - self.m00 * b.m01 + self.m01 * b.m11 + self.m02 * b.m21, - self.m00 * b.m02 + self.m01 * b.m12 + self.m02 * b.m22, - - self.m10 * b.m00 + self.m11 * b.m10 + self.m12 * b.m20, - self.m10 * b.m01 + self.m11 * b.m11 + self.m12 * b.m21, - self.m10 * b.m02 + self.m11 * b.m12 + self.m12 * b.m22, - - self.m20 * b.m00 + self.m21 * b.m10 + self.m22 * b.m20, - self.m20 * b.m01 + self.m21 * b.m11 + self.m22 * b.m21, - self.m20 * b.m02 + self.m21 * b.m12 + self.m22 * b.m22, - }; + return { .m = $$matrix_mul(self.m, b.m, 3, 3, 3) }; } fn Matrix4x4 Matrix4x4.mul(Matrix4x4* self, Matrix4x4 b) @operator(*) { - return { - self.m00 * b.m00 + self.m01 * b.m10 + self.m02 * b.m20 + self.m03 * b.m30, - self.m00 * b.m01 + self.m01 * b.m11 + self.m02 * b.m21 + self.m03 * b.m31, - self.m00 * b.m02 + self.m01 * b.m12 + self.m02 * b.m22 + self.m03 * b.m32, - self.m00 * b.m03 + self.m01 * b.m13 + self.m02 * b.m23 + self.m03 * b.m33, - - self.m10 * b.m00 + self.m11 * b.m10 + self.m12 * b.m20 + self.m13 * b.m30, - self.m10 * b.m01 + self.m11 * b.m11 + self.m12 * b.m21 + self.m13 * b.m31, - self.m10 * b.m02 + self.m11 * b.m12 + self.m12 * b.m22 + self.m13 * b.m32, - self.m10 * b.m03 + self.m11 * b.m13 + self.m12 * b.m23 + self.m13 * b.m33, - - self.m20 * b.m00 + self.m21 * b.m10 + self.m22 * b.m20 + self.m23 * b.m30, - self.m20 * b.m01 + self.m21 * b.m11 + self.m22 * b.m21 + self.m23 * b.m31, - self.m20 * b.m02 + self.m21 * b.m12 + self.m22 * b.m22 + self.m23 * b.m32, - self.m20 * b.m03 + self.m21 * b.m13 + self.m22 * b.m23 + self.m23 * b.m33, - - self.m30 * b.m00 + self.m31 * b.m10 + self.m32 * b.m20 + self.m33 * b.m30, - self.m30 * b.m01 + self.m31 * b.m11 + self.m32 * b.m21 + self.m33 * b.m31, - self.m30 * b.m02 + self.m31 * b.m12 + self.m32 * b.m22 + self.m33 * b.m32, - self.m30 * b.m03 + self.m31 * b.m13 + self.m32 * b.m23 + self.m33 * b.m33, - }; + return { .m = $$matrix_mul(self.m, b.m, 4, 4, 4) }; } -fn Matrix2x2 Matrix2x2.component_mul(&self, Real s) @operator(*) => matrix_component_mul(self, s); -fn Matrix3x3 Matrix3x3.component_mul(&self, Real s) @operator(*) => matrix_component_mul(self, s); -fn Matrix4x4 Matrix4x4.component_mul(&self, Real s) @operator(*) => matrix_component_mul(self, s); +fn Matrix2x2 Matrix2x2.component_mul(&self, Real v) @operator(*) => { .m = self.m * v }; +fn Matrix3x3 Matrix3x3.component_mul(&self, Real v) @operator(*) => { .m = self.m * v }; +fn Matrix4x4 Matrix4x4.component_mul(&self, Real v) @operator(*) => { .m = self.m * v }; -fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2) @operator(+) => matrix_add(self, mat2); -fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2) @operator(+) => matrix_add(self, mat2); -fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2) @operator(+) => matrix_add(self, mat2); +fn void Matrix2x2.component_mul_inplace(&self, Real v) @operator(*=) => self.m *= v; +fn void Matrix3x3.component_mul_inplace(&self, Real v) @operator(*=) => self.m *= v; +fn void Matrix4x4.component_mul_inplace(&self, Real v) @operator(*=) => self.m *= v; -fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2) @operator(-) => matrix_sub(self, mat2); -fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2) @operator(-) => matrix_sub(self, mat2); -fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2) @operator(-) => matrix_sub(self, mat2); +fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2) @operator(+) => { .m = self.m + mat2.m }; +fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2) @operator(+) => { .m = self.m + mat2.m }; +fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2) @operator(+) => { .m = self.m + mat2.m }; -fn Matrix2x2 Matrix2x2.negate(&self) @operator(-) => { .m = (Real[<4>])self.m }; -fn Matrix3x3 Matrix3x3.negate(&self) @operator(-) => { .m = (Real[<9>])self.m }; -fn Matrix4x4 Matrix4x4.negate(&self) @operator(-) => { .m = (Real[<16>])self.m }; +fn void Matrix2x2.add_inplace(&self, Matrix2x2 mat2) @operator(+=) => self.m += mat2.m; +fn void Matrix3x3.add_inplace(&self, Matrix3x3 mat2) @operator(+=) => self.m += mat2.m; +fn void Matrix4x4.add_inplace(&self, Matrix4x4 mat2) @operator(+=) => self.m += mat2.m; -fn bool Matrix2x2.eq(&self, Matrix2x2 mat2) @operator(==) => (Real[<4>])self.m == (Real[<4>])mat2.m; -fn bool Matrix3x3.eq(&self, Matrix3x3 mat2) @operator(==) => (Real[<9>])self.m == (Real[<9>])mat2.m; -fn bool Matrix4x4.eq(&self, Matrix4x4 mat2) @operator(==) => (Real[<16>])self.m == (Real[<16>])mat2.m; +fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2) @operator(-) => { .m = self.m - mat2.m }; +fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2) @operator(-) => { .m = self.m - mat2.m }; +fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2) @operator(-) => { .m = self.m - mat2.m }; -fn bool Matrix2x2.neq(&self, Matrix2x2 mat2) @operator(!=) => (Real[<4>])self.m != (Real[<4>])mat2.m; -fn bool Matrix3x3.neq(&self, Matrix3x3 mat2) @operator(!=) => (Real[<9>])self.m != (Real[<9>])mat2.m; -fn bool Matrix4x4.neq(&self, Matrix4x4 mat2) @operator(!=) => (Real[<16>])self.m != (Real[<16>])mat2.m; +fn void Matrix2x2.sub_inplace(&self, Matrix2x2 mat2) @operator(-=) => self.m -= mat2.m; +fn void Matrix3x3.sub_inplace(&self, Matrix3x3 mat2) @operator(-=) => self.m -= mat2.m; +fn void Matrix4x4.sub_inplace(&self, Matrix4x4 mat2) @operator(-=) => self.m -= mat2.m; -fn Matrix4x4 look_at(Real[<3>] eye, Real[<3>] target, Real[<3>] up) => matrix_look_at(Matrix4x4, eye, target, up); +fn Matrix2x2 Matrix2x2.negate(&self) @operator(-) => { .m = -self.m }; +fn Matrix3x3 Matrix3x3.negate(&self) @operator(-) => { .m = -self.m }; +fn Matrix4x4 Matrix4x4.negate(&self) @operator(-) => { .m = -self.m }; +fn bool Matrix2x2.eq(&self, Matrix2x2 mat2) @operator(==) => self.m == mat2.m; +fn bool Matrix3x3.eq(&self, Matrix3x3 mat2) @operator(==) => self.m == mat2.m; +fn bool Matrix4x4.eq(&self, Matrix4x4 mat2) @operator(==) => self.m == mat2.m; fn Matrix2x2 Matrix2x2.transpose(&self) { - return { - self.m00, self.m10, - self.m01, self.m11 - }; + return { .m = self.m.xzyw }; } fn Matrix3x3 Matrix3x3.transpose(&self) { - return { - self.m00, self.m10, self.m20, - self.m01, self.m11, self.m21, - self.m02, self.m12, self.m22, - }; + return { .m = swizzle(self.m, 0, 3, 6, 1, 4, 7, 2, 5, 8) }; } fn Matrix4x4 Matrix4x4.transpose(&self) { - return { - self.m00, self.m10, self.m20, self.m30, - self.m01, self.m11, self.m21, self.m31, - self.m02, self.m12, self.m22, self.m32, - self.m03, self.m13, self.m23, self.m33, - }; + return { .m = swizzle(self.m, 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15) }; } - -fn Real Matrix2x2.determinant(&self) +fn Real Matrix2x2.determinant(self) { - return self.m00 * self.m11 - self.m01 * self.m10; + Real[<4>] m = self.m; + return m.x * m.w - m.y * m.z; } fn Real Matrix3x3.determinant(&self) { + Real[<9>] m = self.m; return - self.m00 * (self.m11 * self.m22 - self.m21 * self.m12) - - self.m01 * (self.m10 * self.m22 - self.m20 * self.m12) + - self.m02 * (self.m10 * self.m21 - self.m20 * self.m11); + m[0] * (m[4] * m[8] - m[5] * m[7]) - + m[3] * (m[1] * m[8] - m[2] * m[7]) + + m[6] * (m[1] * m[5] - m[2] * m[4]); } fn Real Matrix4x4.determinant(&self) { + Real[<16>] m = self.m; return - self.m00 * (self.m11 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m12 * (self.m21 * self.m33 - self.m31 * self.m23) + - self.m13 * (self.m21 * self.m32 - self.m31 * self.m22) ) - - self.m01 * (self.m10 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m12 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m13 * (self.m20 * self.m32 - self.m30 * self.m22) ) + - self.m02 * (self.m10 * (self.m21 * self.m33 - self.m31 * self.m23) - - self.m11 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m13 * (self.m20 * self.m31 - self.m30 * self.m21) ) - - self.m03 * (self.m10 * (self.m21 * self.m32 - self.m31 * self.m22) - - self.m11 * (self.m20 * self.m32 - self.m30 * self.m22) + - self.m12 * (self.m20 * self.m31 - self.m30 * self.m21) ); -} - - -fn Matrix2x2 Matrix2x2.adjoint(&self) -{ - return { self.m11, -self.m01, -self.m10, self.m00 }; + m[0] * ( + m[5] * (m[10] * m[15] - m[11] * m[14]) - + m[9] * (m[6] * m[15] - m[7] * m[14]) + + m[13] * (m[6] * m[11] - m[7] * m[10]) + ) + - m[4] * ( + m[1] * (m[10] * m[15] - m[11] * m[14]) - + m[9] * (m[2] * m[15] - m[3] * m[14]) + + m[13] * (m[2] * m[11] - m[3] * m[10]) + ) + + m[8] * ( + m[1] * (m[6] * m[15] - m[7] * m[14]) - + m[5] * (m[2] * m[15] - m[3] * m[14]) + + m[13] * (m[2] * m[7] - m[3] * m[6]) + ) + - m[12] * ( + m[1] * (m[6] * m[11] - m[7] * m[10]) - + m[5] * (m[2] * m[11] - m[3] * m[10]) + + m[9] * (m[2] * m[7] - m[3] * m[6]) + ); +} + +fn Matrix2x2 Matrix2x2.adjoint(self) +{ + return { .m = { self.m.w, -self.m.y, -self.m.z, self.m.x } }; } fn Matrix3x3 Matrix3x3.adjoint(&self) { - return { - (self.m11 * self.m22 - self.m21 * self.m12), - -(self.m10 * self.m22 - self.m20 * self.m12), - (self.m10 * self.m21 - self.m20 * self.m11), - - -(self.m01 * self.m22 - self.m21 * self.m02), - (self.m00 * self.m22 - self.m20 * self.m02), - -(self.m00 * self.m21 - self.m20 * self.m01), - - (self.m01 * self.m12 - self.m11 * self.m02), - -(self.m00 * self.m12 - self.m10 * self.m02), - (self.m00 * self.m11 - self.m10 * self.m01), - }; + Real[<9>] m = self.m; + return { .m = { + (m[4] * m[8] - m[5] * m[7]), + -(m[1] * m[8] - m[2] * m[7]), + (m[1] * m[5] - m[2] * m[4]), + + -(m[3] * m[8] - m[5] * m[6]), + (m[0] * m[8] - m[2] * m[6]), + -(m[0] * m[5] - m[2] * m[3]), + + (m[3] * m[7] - m[4] * m[6]), + -(m[0] * m[7] - m[1] * m[6]), + (m[0] * m[4] - m[1] * m[3]), + } }; } + fn Matrix4x4 Matrix4x4.adjoint(&self) { - return { - (self.m11 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m12 * (self.m21 * self.m33 - self.m31 * self.m23) + - self.m13 * (self.m21 * self.m32 - self.m31 * self.m22)), - -(self.m10 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m12 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m13 * (self.m20 * self.m32 - self.m30 * self.m22)), - (self.m10 * (self.m21 * self.m33 - self.m31 * self.m23) - - self.m11 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m13 * (self.m20 * self.m31 - self.m30 * self.m21)), - -(self.m10 * (self.m21 * self.m32 - self.m31 * self.m22) - - self.m11 * (self.m20 * self.m32 - self.m30 * self.m22) + - self.m12 * (self.m20 * self.m31 - self.m30 * self.m21)), - - -(self.m01 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m02 * (self.m21 * self.m33 - self.m31 * self.m23) + - self.m03 * (self.m21 * self.m32 - self.m31 * self.m22)), - (self.m00 * (self.m22 * self.m33 - self.m32 * self.m23) - - self.m02 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m03 * (self.m20 * self.m32 - self.m30 * self.m22)), - -(self.m00 * (self.m21 * self.m33 - self.m31 * self.m23) - - self.m01 * (self.m20 * self.m33 - self.m30 * self.m23) + - self.m03 * (self.m20 * self.m31 - self.m30 * self.m21)), - (self.m00 * (self.m21 * self.m32 - self.m31 * self.m22) - - self.m01 * (self.m20 * self.m32 - self.m30 * self.m22) + - self.m02 * (self.m20 * self.m31 - self.m30 * self.m21)), - - (self.m01 * (self.m12 * self.m33 - self.m32 * self.m13) - - self.m02 * (self.m11 * self.m33 - self.m31 * self.m13) + - self.m03 * (self.m11 * self.m32 - self.m31 * self.m12)), - -(self.m00 * (self.m12 * self.m33 - self.m32 * self.m13) - - self.m02 * (self.m10 * self.m33 - self.m30 * self.m13) + - self.m03 * (self.m10 * self.m32 - self.m30 * self.m12)), - (self.m00 * (self.m11 * self.m33 - self.m31 * self.m13) - - self.m01 * (self.m10 * self.m33 - self.m30 * self.m13) + - self.m03 * (self.m10 * self.m31 - self.m30 * self.m11)), - -(self.m00 * (self.m11 * self.m32 - self.m31 * self.m12) - - self.m01 * (self.m10 * self.m32 - self.m30 * self.m12) + - self.m02 * (self.m10 * self.m31 - self.m30 * self.m11)), - - -(self.m01 * (self.m12 * self.m23 - self.m22 * self.m13) - - self.m02 * (self.m11 * self.m23 - self.m21 * self.m13) + - self.m03 * (self.m11 * self.m22 - self.m21 * self.m12)), - (self.m00 * (self.m12 * self.m23 - self.m22 * self.m13) - - self.m02 * (self.m10 * self.m23 - self.m20 * self.m13) + - self.m03 * (self.m10 * self.m22 - self.m20 * self.m12)), - -(self.m00 * (self.m11 * self.m23 - self.m21 * self.m13) - - self.m01 * (self.m10 * self.m23 - self.m20 * self.m13) + - self.m03 * (self.m10 * self.m21 - self.m20 * self.m11)), - (self.m00 * (self.m11 * self.m22 - self.m21 * self.m12) - - self.m01 * (self.m10 * self.m22 - self.m20 * self.m12) + - self.m02 * (self.m10 * self.m21 - self.m20 * self.m11)), - }; + Real[<16>] m = self.m; + return { .m = { + (m[5] * (m[10] * m[15] - m[11] * m[14]) - + m[9] * (m[6] * m[15] - m[7] * m[14]) + + m[13] * (m[6] * m[11] - m[7] * m[10])), + + -(m[1] * (m[10] * m[15] - m[11] * m[14]) - + m[9] * (m[2] * m[15] - m[3] * m[14]) + + m[13] * (m[2] * m[11] - m[3] * m[10])), + + (m[1] * (m[6] * m[15] - m[7] * m[14]) - + m[5] * (m[2] * m[15] - m[3] * m[14]) + + m[13] * (m[2] * m[7] - m[3] * m[6])), + + -(m[1] * (m[6] * m[11] - m[7] * m[10]) - + m[5] * (m[2] * m[11] - m[3] * m[10]) + + m[9] * (m[2] * m[7] - m[3] * m[6])), + + -(m[4] * (m[10] * m[15] - m[11] * m[14]) - + m[8] * (m[6] * m[15] - m[7] * m[14]) + + m[12] * (m[6] * m[11] - m[7] * m[10])), + + (m[0] * (m[10] * m[15] - m[11] * m[14]) - + m[8] * (m[2] * m[15] - m[3] * m[14]) + + m[12] * (m[2] * m[11] - m[3] * m[10])), + + -(m[0] * (m[6] * m[15] - m[7] * m[14]) - + m[4] * (m[2] * m[15] - m[3] * m[14]) + + m[12] * (m[2] * m[7] - m[3] * m[6])), + + (m[0] * (m[6] * m[11] - m[7] * m[10]) - + m[4] * (m[2] * m[11] - m[3] * m[10]) + + m[8] * (m[2] * m[7] - m[3] * m[6])), + + (m[4] * (m[9] * m[15] - m[11] * m[13]) - + m[8] * (m[5] * m[15] - m[7] * m[13]) + + m[12] * (m[5] * m[11] - m[7] * m[9])), + + -(m[0] * (m[9] * m[15] - m[11] * m[13]) - + m[8] * (m[1] * m[15] - m[3] * m[13]) + + m[12] * (m[1] * m[11] - m[3] * m[9])), + + (m[0] * (m[5] * m[15] - m[7] * m[13]) - + m[4] * (m[1] * m[15] - m[3] * m[13]) + + m[12] * (m[1] * m[7] - m[3] * m[5])), + + -(m[0] * (m[5] * m[11] - m[7] * m[9]) - + m[4] * (m[1] * m[11] - m[3] * m[9]) + + m[8] * (m[1] * m[7] - m[3] * m[5])), + + -(m[4] * (m[9] * m[14] - m[10] * m[13]) - + m[8] * (m[5] * m[14] - m[6] * m[13]) + + m[12] * (m[5] * m[10] - m[6] * m[9])), + + (m[0] * (m[9] * m[14] - m[10] * m[13]) - + m[8] * (m[1] * m[14] - m[2] * m[13]) + + m[12] * (m[1] * m[10] - m[2] * m[9])), + + -(m[0] * (m[5] * m[14] - m[6] * m[13]) - + m[4] * (m[1] * m[14] - m[2] * m[13]) + + m[12] * (m[1] * m[6] - m[2] * m[5])), + + (m[0] * (m[5] * m[10] - m[6] * m[9]) - + m[4] * (m[1] * m[10] - m[2] * m[9]) + + m[8] * (m[1] * m[6] - m[2] * m[5])), + } }; } @@ -322,7 +319,7 @@ fn Matrix2x2? Matrix2x2.inverse(&self) Real det = self.determinant(); if (det == 0) return math::MATRIX_INVERSE_DOESNT_EXIST~; Matrix2x2 adj = self.adjoint(); - return adj.component_mul(1 / det).transpose(); + return adj.component_mul(1 / det); } fn Matrix3x3? Matrix3x3.inverse(&self) @@ -330,7 +327,7 @@ fn Matrix3x3? Matrix3x3.inverse(&self) Real det = self.determinant(); if (det == 0) return math::MATRIX_INVERSE_DOESNT_EXIST~; Matrix3x3 adj = self.adjoint(); - return adj.component_mul(1 / det).transpose(); + return adj.component_mul(1 / det); } fn Matrix4x4? Matrix4x4.inverse(&self) @@ -338,155 +335,193 @@ fn Matrix4x4? Matrix4x4.inverse(&self) Real det = self.determinant(); if (det == 0) return math::MATRIX_INVERSE_DOESNT_EXIST~; Matrix4x4 adj = self.adjoint(); - return adj.component_mul(1 / det).transpose(); + return adj.component_mul(1 / det); } - fn Matrix3x3 Matrix3x3.translate(&self, Real[<2>] v) { - return self.mul({ - 1, 0, v[0], - 0, 1, v[1], - 0, 0, 1, - }); + Real[<9>] m = self.m; + m[6] = m[0] * v.x + m[3] * v.y + m[6]; + m[7] = m[1] * v.x + m[4] * v.y + m[7]; + m[8] = m[2] * v.x + m[5] * v.y + m[8]; + return { .m = m }; } fn Matrix4x4 Matrix4x4.translate(&self, Real[<3>] v) { - return self.mul({ - 1, 0, 0, v[0], - 0, 1, 0, v[1], - 0, 0, 1, v[2], - 0, 0, 0, 1, - }); + Real[<16>] m = self.m; + m[12] = m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12]; + m[13] = m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13]; + m[14] = m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14]; + m[15] = m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15]; + return { .m = m }; } // r in radians fn Matrix3x3 Matrix3x3.rotate(&self, Real r) { - return self.mul({ - math::cos(r), -math::sin(r), 0, - math::sin(r), math::cos(r), 0, - 0, 0, 1, - }); + Real[<2>] sc = math::sincos(r); + return self.mul({ .m = { sc.y, sc.x, 0, -sc.x, sc.y, 0, 0, 0, 1 }}); +} + + +<* + Rotate the matrix (assuming RH system) + + @param angle : "The angle to rotate in radians" + @param axis : "The axis around which to rotate the matrix" + + @return "The resulting rotated matrix" +*> +fn Matrix4x4 Matrix4x4.rotate(&self, Real angle, Real[<3>] axis) +{ + axis = axis.normalize(); + Real[<2>] sincos_theta = math::sincos(angle); + Real s = sincos_theta.x; + Real ct = sincos_theta.y; + Real c = 1 - ct; + + Real x = axis.x; + Real y = axis.y; + Real z = axis.z; + + Real xx = x * x; + Real xy = x * y; + Real xz = x * z; + Real yy = y * y; + Real yz = y * z; + Real zz = z * z; + + return { .m = { + xx * c + ct, xy * c + z * s, xz * c - y * s, 0, + xy * c - z * s, yy * c + ct, yz * c + x * s, 0, + xz * c +y * s, yz * c - x * s, zz * c + ct, 0, + 0, 0, 0, 1 } }; } // r in radians fn Matrix4x4 Matrix4x4.rotate_z(&self, Real r) { - return self.mul({ - math::cos(r), -math::sin(r), 0, 0, - math::sin(r), math::cos(r), 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - }); + Real[<2>] sc = math::sincos(r); + return self.mul({ .m = { sc.y, sc.x, 0, 0, -sc.x, sc.y, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }}); } // r in radians fn Matrix4x4 Matrix4x4.rotate_y(&self, Real r) { - return self.mul({ - math::cos(r), 0, math::sin(r), 0, + Real[<2>] sc = math::sincos(r); + return self.mul({ .m = { + sc.y, 0, -sc.x, 0, 0, 1, 0, 0, - -math::sin(r), 0, math::cos(r), 0, + sc.x, 0, sc.y, 0, 0, 0, 0, 1, - }); + }}); } // r in radians fn Matrix4x4 Matrix4x4.rotate_x(&self, Real r) { - return self.mul({ + Real[<2>] sc = math::sincos(r); + return self.mul({ .m = { 1, 0, 0, 0, - 0, math::cos(r), -math::sin(r), 0, - 0, math::sin(r), math::cos(r), 0, + 0, sc.y, sc.x, 0, + 0, -sc.x, sc.y, 0, 0, 0, 0, 1, - }); + }}); } - -fn Matrix3x3 Matrix3x3.scale(&self, Real[<2>] v) +fn Real Matrix2x2.trace(&self) => self.m.xw.sum(); +fn Real Matrix3x3.trace(&self) { - return self.mul({ - v[0], 0, 0, - 0, v[1], 0, - 0, 0, 1, - }); + Real[<9>] m = self.m; + return m[0] + m[4] + m[8]; } - -fn Real Matrix2x2.trace(&self) => self.m00 + self.m11; -fn Real Matrix3x3.trace(&self) => self.m00 + self.m11 + self.m22; -fn Real Matrix4x4.trace(&self) => self.m00 + self.m11 + self.m22 + self.m33; - -fn Matrix4x4 Matrix4x4.scale(&self, Real[<3>] v) +fn Real Matrix4x4.trace(&self) { - return self.mul({ - v[0], 0, 0, 0, - 0, v[1], 0, 0, - 0, 0, v[2], 0, - 0, 0, 0, 1, - }); + Real[<16>] m = self.m; + return m[0] + m[5] + m[10] + m[15]; } -fn Matrix4x4 ortho(Real left, Real right, Real top, Real bottom, Real near, Real far) +fn Matrix3x3 Matrix3x3.scale(&self, Real[<2>] v) { - Real width = right - left; - Real height = top - bottom; - Real depth = far - near; - return { - 2 / width, 0, 0, -(right + left) / width, - 0, 2 / height, 0, -(top + bottom) / height, - 0, 0, -2 / depth, -(far + near) / depth, - 0, 0, 0, 1 - }; + return { .m = self.m * { v.x, v.x, v.x, v.y, v.y, v.y, 1, 1, 1 } }; } -// fov in radians -fn Matrix4x4 perspective(Real fov, Real aspect_ratio, Real near, Real far) +fn Matrix4x4 Matrix4x4.scale(&self, Real[<3>] v) { - Real f = (Real)math::tan(math::PI * 0.5 - 0.5 * fov); - Real range_inv = (Real)1.0 / (near - far); - - return { - f / aspect_ratio, 0, 0, 0, - 0, f, 0, 0, - 0, 0, (near + far) * range_inv, near * far * range_inv * 2, - 0, 0, -1, 0, - }; + return { .m = self.m * { v.x, v.x, v.x, v.x, v.y, v.y, v.y, v.y, v.z, v.z, v.z, v.z, 1, 1, 1, 1 } }; } -const Matrix2x2 IDENTITY2 = { .m = { [0] = 1, [3] = 1 } }; -const Matrix3x3 IDENTITY3 = { .m = { [0] = 1, [4] = 1, [8] = 1 } }; -const Matrix4x4 IDENTITY4 = { .m = { [0] = 1, [5] = 1, [10] = 1, [15] = 1 } }; - -macro matrix_component_mul(mat, val) @private +fn Matrix4x4 look_at(Real[<3>] eye, Real[<3>] target, Real[<3>] up) { - var $Type = Real[<$typeof(mat.m).len>]; - return ($typeof(*mat)) { .m = val * ($Type)mat.m }; + Real[<3>] vz = (eye - target).normalize(); + Real[<3>] vx = up.cross(vz).normalize(); + Real[<3>] vy = vz.cross(vx); + return { .m = { + vx.x, vy.x, vz.x, 0, + vx.y, vy.y, vz.y, 0, + vx.z, vy.z, vz.z, 0, + -vx.dot(eye), -vy.dot(eye), -vz.dot(eye), 1 + }}; } -macro matrix_add(mat, mat2) @private +// Implementation is RH [0, 1] based on glm's orthoRH_ZO +fn Matrix4x4 ortho(Real left, Real right, Real bottom, Real top, Real z_near, Real z_far) { - var $Type = Real[<$typeof(mat.m).len>]; - return ($typeof(*mat)) { .m = ($Type)mat.m + ($Type)mat2.m }; + Real width_inv = 1 / (right - left); + Real height_inv = 1 / (top - bottom); + Real depth_inv = 1 / (z_far - z_near); + return { .m = { + [0] = 2 * width_inv, + [5] = 2 * height_inv, + [10] = - depth_inv, + [12] = - (right + left) * width_inv, + [13] = - (top + bottom) * height_inv, + [14] = - z_near * depth_inv, + [15] = 1, } }; } -macro matrix_sub(mat, mat2) @private +// Implements right-handed [0, 1] perspective +fn Matrix4x4 perspective(Real fovy, Real aspect, Real z_near, Real z_far) { - var $Type = Real[<$typeof(mat.m).len>]; - return ($typeof(*mat)) { .m = ($Type)mat.m - ($Type)mat2.m }; + Real tan_half_fovy = math::tan(fovy / 2); + + Real fn_scale = 1 / (z_near - z_far); + + return (Matrix4x4) { .m = { + [0] = 1 / (aspect * tan_half_fovy), + [5] = 1 / (tan_half_fovy), + [10] = z_far * fn_scale, + [11] = -1, + [14] = z_far * z_near * fn_scale, + } }; } -macro matrix_look_at($Type, eye, target, up) @private +// Implements right-handed [0, 1] perspective +<* + @require left != right : "Left and right cannot be the same" + @require bottom != top : "Bottom and top cannot be the same" + @require z_near != z_far : "Near and far cannot be the same" +*> +fn Matrix4x4 frustum(Real left, Real right, Real bottom, Real top, Real z_near, Real z_far) { - var vz = (eye - target).normalize(); - var vx = up.cross(vz).normalize(); - var vy = vz.cross(vx); + Real fn_scale = 1 / (z_near - z_far); + Real rl_scale = 1 / (right - left); + Real tb_scale = 1 / (top - bottom); - return ($Type){ - vx[0], vx[1], vx[2], - (Real)vx.dot(eye), - vy[0], vy[1], vy[2], - (Real)vy.dot(eye), - vz[0], vz[1], vz[2], - (Real)vz.dot(eye), - 0.0, 0.0, 0.0, 1 - }; + return (Matrix4x4) { .m = { + [0] = (2 * z_near) * rl_scale, + [5] = (2 * z_near) * tb_scale, + [8] = (right + left) * rl_scale, + [9] = (top + bottom) * tb_scale, + [10] = z_far * fn_scale, + [11] = -1, + [14] = z_far * z_near * fn_scale, + } }; } + +const Matrix2x2 IDENTITY2 = { .m = { 1, 0, 0, 1 } }; +const Matrix3x3 IDENTITY3 = { .m = { [0] = 1, [4] = 1, [8] = 1 } }; +const Matrix4x4 IDENTITY4 = { .m = { [0] = 1, [5] = 1, [10] = 1, [15] = 1 } }; + + diff --git a/test/stdlib/src/math/quaternion.c3 b/test/stdlib/src/math/quaternion.c3 index a7f4168..d032568 100644 --- a/test/stdlib/src/math/quaternion.c3 +++ b/test/stdlib/src/math/quaternion.c3 @@ -1,21 +1,20 @@ -module std::math; +module std::math::quaternion; // Predefined quaternion aliases. - alias Quaternionf = QuaternionNumber {float}; alias Quaternion = QuaternionNumber {double}; -alias QUATERNION_IDENTITY @builtin = quaternion::IDENTITY {double}; -alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float}; + +alias Quat = QuaternionNumber {float}; <* The generic quaternion module, for float or double based quaternion definitions. - @require Real.kindof == FLOAT : "A quaternion must use a floating type" + @require Real::kind == FLOAT : "A quaternion must use a floating type" *> - module std::math::quaternion ; -import std::math::vector; -union QuaternionNumber +import std::math::vector, std::math::matrix, std::io; + +union QuaternionNumber (Printable) { struct { @@ -24,6 +23,11 @@ union QuaternionNumber Real[<4>] v; } +fn sz? QuaternionNumber.to_format(&self, Formatter* f) @dynamic +{ + return f.printf("%s", self.v); +} + const QuaternionNumber IDENTITY = { 0, 0, 0, 1 }; macro QuaternionNumber QuaternionNumber.add(self, QuaternionNumber b) @operator(+) => { .v = self.v + b.v }; @@ -34,12 +38,15 @@ macro QuaternionNumber QuaternionNumber.sub_each(self, Real b) => { .v = self.v macro QuaternionNumber QuaternionNumber.scale(self, Real s) @operator_s(*) => { .v = self.v * s }; macro QuaternionNumber.to_angle(self) => 2 * math::acos(self.v.w); macro QuaternionNumber QuaternionNumber.normalize(self) => { .v = self.v.normalize() }; +fn bool QuaternionNumber.is_normalized(self) => self.v.is_normalized(); +macro bool QuaternionNumber.equals(self, QuaternionNumber other) => self.v.equals(other.v); +macro bool QuaternionNumber.not_equals(self, QuaternionNumber other) => self.v.not_equals(other.v); macro Real QuaternionNumber.length(self) => self.v.length(); +macro Real QuaternionNumber.length_sq(self) => self.v.length_sq(); macro QuaternionNumber QuaternionNumber.lerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount) }; fn QuaternionNumber QuaternionNumber.nlerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount).normalize() }; -macro Matrix4f QuaternionNumber.to_matrixf(&self) => into_matrix(self, Matrix4f); -macro Matrix4 QuaternionNumber.to_matrix(&self) => into_matrix(self, Matrix4); +macro Matrix4x4{Real} QuaternionNumber.to_matrix(&self) => into_matrix(self, Matrix4x4{Real}); fn QuaternionNumber QuaternionNumber.invert(self) @@ -50,7 +57,19 @@ fn QuaternionNumber QuaternionNumber.invert(self) return { self.v[0] * -inv_length, self.v[1] * -inv_length, self.v[2] * -inv_length, self.v[3] * inv_length }; } -fn QuaternionNumber QuaternionNumber.conjugate(&self) => { -self.v.x, -self.v.y, -self.v.z, self.v.w }; +fn QuaternionNumber QuaternionNumber.transform(self, Matrix4x4{Real} transform) +{ + Real[<4>] v = self.v; + Real[<16>] m = transform.m; + return { + m[0] * v.x + m[1] * v.y + m[2] * v.z + m[3] * v.w, + m[4] * v.x + m[5] * v.y + m[6] * v.z + m[7] * v.w, + m[8] * v.x + m[9] * v.y + m[10] * v.z + m[11] * v.w, + m[12] * v.x + m[13] * v.y + m[14] * v.z + m[15] * v.w, + }; +} + +fn QuaternionNumber QuaternionNumber.conjugate(self) => { -self.v.x, -self.v.y, -self.v.z, self.v.w }; fn QuaternionNumber QuaternionNumber.slerp(self, QuaternionNumber q2, Real amount) { @@ -93,6 +112,47 @@ fn QuaternionNumber QuaternionNumber.mul(self, QuaternionNumber b) @operator(*) } +// Get the quaternion equivalent to Euler angles +// Rotation order is ZYX +fn QuaternionNumber from_euler(Real pitch, Real yaw, Real roll) +{ + Real x0 = math::cos(pitch / 2); + Real x1 = math::sin(pitch / 2); + Real y0 = math::cos(yaw / 2); + Real y1 = math::sin(yaw / 2); + Real z0 = math::cos(roll / 2); + Real z1 = math::sin(roll / 2); + + return { + x1*y0*z0 - x0*y1*z1, + x0*y1*z0 + x1*y0*z1, + x0*y0*z1 - x1*y1*z0, + x0*y0*z0 + x1*y1*z1 + }; +} + +// +<* + Get the Euler angles equivalent to quaternion (roll, pitch, yaw) + + @require math::is_approx(self.length_sq(), 1, QUATERNION_EPSILON) : "Expected a normalized quaternion" + @return "a vector containing { roll, pitch, yaw }" +*> +fn Real[<3>] QuaternionNumber.to_euler(self) +{ + Real[<4>] q = self.v; + // Roll (x-axis rotation) + Real x0 = 2 * (q.w * q.x + q.y * q.z); + Real x1 = 1 - 2 * (q.x * q.x + q.y * q.y); + // Pitch (y-axis rotation) + Real y0 = 2 * (q.w * q.y - q.z * q.x); + y0 = math::clamp(y0, -1, 1); + // Yaw (z-axis rotation) + Real z0 = 2 * (q.w * q.z + q.x * q.y); + Real z1 = 1 - 2 * (q.y * q.y + q.z * q.z); + return { math::atan2(x0, x1), math::asin(y0), math::atan2(z0, z1) }; +} + fn QuaternionNumber from_axis_angle(Real[<3>] axis, Real angle) { Real[<3>] normal_axis = axis.normalize(); @@ -102,6 +162,22 @@ fn QuaternionNumber from_axis_angle(Real[<3>] axis, Real angle) return { ...(normal_axis * sin_half), math::cos(half_angle) }; } +<* + @return "First 3 components is the axis, the last component is the angle" +*> +fn Real[<4>] QuaternionNumber.to_axis_angle(self) +{ + Real[<4>] v = self.v; + if (math::abs(v.w) > 1) + { + v = v.normalize(); + } + + Real den = math::sqrt(1 - v.w * v.w); + // Default to 1,0,0 when small + return { ...(den > QUATERNION_EPSILON ? v.xyz / den : { 1, 0, 0 }), 2 * math::acos(v.w) }; +} + fn Real[<3>] QuaternionNumber.rotate_vec3(self, Real[<3>] vector) @operator(*) { QuaternionNumber p = { ...vector, 0 }; @@ -109,20 +185,58 @@ fn Real[<3>] QuaternionNumber.rotate_vec3(self, Real[<3>] vector) @operator(*) return result.v.xyz; } +<* + Create a quaternion transform from `from` to `to`, assuming they are not parallel and that they are + normalized. Use `from_rotation` for non-normalized vectors. + + @require from.is_normalized() : "Expected 'from' to be normalized" + @require to.is_normalized() : "Expected 'to' to be normalized" + @ensure return.is_normalized() +*> +fn QuaternionNumber from_normalized_rotation(Real[<3>] from, Real[<3>] to) +{ + return (QuaternionNumber){ ...from.cross(to), 1 + from.dot(to) }.normalize(); +} + +<* + Create a quaternion transform from `from` to `to`, assuming they are not parallel. + Use `from_normalized_rotation` for normalized vectors. + + @ensure return.is_normalized() +*> +fn QuaternionNumber from_rotation(Real[<3>] from, Real[<3>] to) +{ + Real cos2_theta = from.dot(to); + Real[<3>] cross = from.cross(to); + QuaternionNumber num = { ...cross, math::sqrt(cross.length_sq() + cos2_theta * cos2_theta) + cos2_theta }; + return num.normalize(); +} + macro into_matrix(QuaternionNumber* q, $Type) @private { - QuaternionNumber rotation = q.normalize(); - var x = rotation.i; - var y = rotation.j; - var z = rotation.k; - var w = rotation.l; - - return ($Type) { - 1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0, - 2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0, - 2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x*x - 2*y*y, 0, - 0.0, 0.0, 0.0, 1.0, - }; -} \ No newline at end of file + QuaternionNumber rotation = q.normalize(); + var x = rotation.i; + var y = rotation.j; + var z = rotation.k; + var w = rotation.l; + + $if $feature(OLD_MATRIX): + return ($Type) { + 1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w, 0, + 2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w, 0, + 2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x*x - 2*y*y, 0, + 0.0, 0.0, 0.0, 1.0, + }; + $else + return ($Type) { .m = { + 1 - 2*y*y - 2*z*z, 2*x*y + 2*z*w, 2*x*z - 2*y*w, 0, + 2*x*y - 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z + 2*x*w, 0, + 2*x*z + 2*y*w, 2*y*z - 2*x*w, 1 - 2*x*x - 2*y*y, 0, + 0.0, 0.0, 0.0, 1.0, + } }; + $endif +} + +const QUATERNION_EPSILON @private = Real::kind == FLOAT ? math::FLOAT_VECTOR_EPSILON : math::DOUBLE_VECTOR_EPSILON; diff --git a/test/stdlib/src/math/random.c3 b/test/stdlib/src/math/random.c3 index b61d70a..c0031f2 100644 --- a/test/stdlib/src/math/random.c3 +++ b/test/stdlib/src/math/random.c3 @@ -34,7 +34,7 @@ macro void seed_entropy(random) @require is_random(random) @require range > 0 *> -macro int next(random, uint range) +macro int next(random, int range) { if (range == 1) return 0; uint mask = ~0U; @@ -46,7 +46,7 @@ macro int next(random, uint range) x = random.next_int() & mask; } while (x > range); - return x; + return (int)x; } <* @@ -96,7 +96,7 @@ fn int rand_in_range(int min, int max) @builtin fn double rnd() @builtin { init_default_random(); - ulong val = default_random.next_long() & (1UL << 53 - 1); + ulong val = default_random.next_long() & (1UL << 53 - 1u); return val * 0x1.0p-53; } @@ -117,7 +117,7 @@ macro bool next_bool(random) *> macro float next_float(random) { - uint val = random.next_int() & (1U << 24 - 1); + uint val = random.next_int() & (1U << 24 - 1u); return val * 0x1.0p-24f; } @@ -128,7 +128,7 @@ macro float next_float(random) *> macro double next_double(random) { - ulong val = random.next_long() & (1UL << 53 - 1); + ulong val = random.next_long() & (1UL << 53 - 1u); return val * 0x1.0p-53; } @@ -142,8 +142,8 @@ macro ushort @char_to_short(#function) => (ushort)#function << 8 + #function; macro void @random_value_to_bytes(#function, char[] bytes) { - var $byte_size = $sizeof(#function()); - usz len = bytes.len; + var $byte_size = @sizeof(#function()); + sz len = bytes.len; // Same size or smaller? Then just copy. while (len > 0) { diff --git a/test/stdlib/src/math/random/math.mcg.c3 b/test/stdlib/src/math/random/math.mcg.c3 index a82e90b..c29ba05 100644 --- a/test/stdlib/src/math/random/math.mcg.c3 +++ b/test/stdlib/src/math/random/math.mcg.c3 @@ -11,7 +11,7 @@ typedef Mcg128Random (Random) = uint128; fn void Mcg128Random.set_seed(&self, char[] seed) @dynamic { - *self = (Mcg128Random)(random::make_seed(uint128, seed) | 1); + *self = (Mcg128Random)(random::make_seed(uint128, seed) | 1u); } <* diff --git a/test/stdlib/src/math/random/math.seeder.c3 b/test/stdlib/src/math/random/math.seeder.c3 index 93ecee2..2491518 100644 --- a/test/stdlib/src/math/random/math.seeder.c3 +++ b/test/stdlib/src/math/random/math.seeder.c3 @@ -23,23 +23,23 @@ fn void seeder(char[] input, char[] out_buffer) $echo("Please note that the seeding function behaves differently on BE architectures."); $endif // Init words - usz out_chars = out_buffer.len; - @pool() + sz out_chars = out_buffer.len; + @stack_mem(550; mem) { - ulong[] words = mem::talloc_array(ulong, (out_chars + 7) / 8); + ulong[] words = alloc::alloc_array(mem, ulong, (out_chars + 7) / 8); words[..] = ODD_PHI64; - usz words_len_2 = words.len * 2; + sz words_len_2 = words.len * 2; // Add word at a time - for (usz i = 0; i < input.len / 8; i++) + for (sz i = 0; i < input.len / 8; i++) { - usz j = i % words.len; + sz j = i % (sz)words.len; words[j] -= bitcast(*(char[8]*)&input[i * 8], ulong) * MUL_LCG64; words[j] ^= words[j] >> 25; } // Add rest of the bytes - usz remaining = input.len - input.len / 8 * 8; + sz remaining = input.len - input.len / 8 * 8; if (remaining) { ulong rest = MUL_MCG64; @@ -49,17 +49,17 @@ fn void seeder(char[] input, char[] out_buffer) } // Mix between words - for (isz i = words_len_2 - 1; i >= 0; i--) + for (sz i = words_len_2 - 1; i >= 0; i--) { - isz j = i % words.len; + sz j = i % words.len; words[j] -= words[(i + 1) % words.len] * MUL_LCG64; words[j] ^= words[j] >> 25; } // Mix within words - for (usz i = 0; i < words_len_2; i++) + for (sz i = 0; i < words_len_2; i++) { - usz j = i % words.len; + sz j = i % (sz)words.len; words[j] *= MUL_MCG64; words[j] ^= words[j] >> 25; } @@ -69,7 +69,7 @@ fn void seeder(char[] input, char[] out_buffer) macro uint hash(value) @local { - return (uint)a5hash::hash(&&bitcast(value, char[$sizeof(value)])); + return (uint)a5hash::hash(&&bitcast(value, char[@sizeof(value)])); } fn char[8 * 4] entropy() @if(!env::FREESTANDING_WASM) diff --git a/test/stdlib/src/math/random/math.simple_random.c3 b/test/stdlib/src/math/random/math.simple_random.c3 index 4a695f1..37d3bdb 100644 --- a/test/stdlib/src/math/random/math.simple_random.c3 +++ b/test/stdlib/src/math/random/math.simple_random.c3 @@ -29,6 +29,6 @@ fn ulong SimpleRandom.next_long(&self) @dynamic => @int_to_long(self.next_int()) fn ushort SimpleRandom.next_short(&self) @dynamic => (ushort)self.next_int(); fn char SimpleRandom.next_byte(&self) @dynamic => (char)self.next_int(); -const long SIMPLE_RANDOM_MULTIPLIER @local = 0x5DEECE66D; -const long SIMPLE_RANDOM_ADDEND @local = 0xB; -const long SIMPLE_RANDOM_MASK @local = (1UL << 48) - 1; +const ulong SIMPLE_RANDOM_MULTIPLIER @local = 0x5DEECE66D; +const ulong SIMPLE_RANDOM_ADDEND @local = 0xB; +const ulong SIMPLE_RANDOM_MASK @local = (1UL << 48) - 1u; diff --git a/test/stdlib/src/math/random/math.xorshiro.c3 b/test/stdlib/src/math/random/math.xorshiro.c3 deleted file mode 100644 index f553a55..0000000 --- a/test/stdlib/src/math/random/math.xorshiro.c3 +++ /dev/null @@ -1,42 +0,0 @@ -module std::math::random; - -struct Xorshiro128PPRandom (Random) -{ - uint[4] state; -} - -<* - @require seed.len > 0 -*> -fn void Xorshiro128PPRandom.set_seed(&self, char[] seed) @dynamic -{ - self.state = random::make_seed(uint[4], seed); -} - -// Xorshiro128++ implementation -fn uint Xorshiro128PPRandom.next_int(&self) @dynamic -{ - uint result = (self.state[0] + self.state[3]).rotl(7) + self.state[0]; - - uint t = self.state[1] << 9U; - - self.state[2] ^= self.state[0]; - self.state[3] ^= self.state[1]; - self.state[1] ^= self.state[2]; - self.state[0] ^= self.state[3]; - - self.state[2] ^= t; - - self.state[3] = self.state[3].rotl(11); - - return result; -} - -<* - @require bytes.len > 0 -*> -fn void Xorshiro128PPRandom.next_bytes(&self, char[] bytes) @dynamic => @random_value_to_bytes(self.next_int, bytes); -fn uint128 Xorshiro128PPRandom.next_int128(&self) @dynamic => @long_to_int128(self.next_long()); -fn ulong Xorshiro128PPRandom.next_long(&self) @dynamic => @int_to_long(self.next_int()); -fn ushort Xorshiro128PPRandom.next_short(&self) @dynamic => (ushort)self.next_int(); -fn char Xorshiro128PPRandom.next_byte(&self) @dynamic => (char)self.next_int(); diff --git a/test/stdlib/src/math/random/math.xoshiro.c3 b/test/stdlib/src/math/random/math.xoshiro.c3 new file mode 100644 index 0000000..c5ece4c --- /dev/null +++ b/test/stdlib/src/math/random/math.xoshiro.c3 @@ -0,0 +1,243 @@ +// Copyright (c) 2026 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// For more information about the various Blackman-Vigna Xorshift PRNGs, see: https://prng.di.unimi.it/ +// Also: https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf +// +module std::math::random; + +import std::math::random::xoshiro @public; +import std::math::random::xoroshiro @public; + +enum XorshiftType @private +{ + PLUS, + PLUS_PLUS, + STAR, + STAR_STAR, +} + +macro void @xorshift_seed_from_input(#xorshift_instance, sz $bits, seed) +{ + $switch $typeof(seed): + $case char[]: + if (!seed.len) return #xorshift_instance.set_seed_long(0); + #xorshift_instance.state = random::make_seed($typeof(#xorshift_instance.state), seed); + $default: + $switch: + $case $bits == 64: + #xorshift_instance.long_state = seed ^ SILVER_RATIO_64; + $case $bits == 128 &&& $defined(#xorshift_instance.long_state): + #xorshift_instance.long_state[0] = (seed ^= SILVER_RATIO_64); + #xorshift_instance.long_state[1] = seed + GOLDEN_RATIO_64; + $default: + #xorshift_instance.state[0] = (seed ^= SILVER_RATIO_64); + foreach (&s : #xorshift_instance.state[1..]) *s = (seed += GOLDEN_RATIO_64); + $endswitch + $endswitch +} + +alias Xoshiro128p = Xoshiro{128, XorshiftType.PLUS}; +alias Xoshiro128pp = Xoshiro{128, XorshiftType.PLUS_PLUS}; +alias Xoshiro128ss = Xoshiro{128, XorshiftType.STAR_STAR}; + +alias Xoshiro256p = Xoshiro{256, XorshiftType.PLUS}; +alias Xoshiro256pp = Xoshiro{256, XorshiftType.PLUS_PLUS}; +alias Xoshiro256ss = Xoshiro{256, XorshiftType.STAR_STAR}; + +alias Xoshiro512p = Xoshiro{512, XorshiftType.PLUS}; +alias Xoshiro512pp = Xoshiro{512, XorshiftType.PLUS_PLUS}; +alias Xoshiro512ss = Xoshiro{512, XorshiftType.STAR_STAR}; + +alias Xoroshiro64s = Xoroshiro{64, XorshiftType.STAR}; +alias Xoroshiro64ss = Xoroshiro{64, XorshiftType.STAR_STAR}; + +alias Xoroshiro128p = Xoroshiro{128, XorshiftType.PLUS}; +alias Xoroshiro128pp = Xoroshiro{128, XorshiftType.PLUS_PLUS}; +alias Xoroshiro128ss = Xoroshiro{128, XorshiftType.STAR_STAR}; + +alias Xoroshiro1024s = Xoroshiro{1024, XorshiftType.STAR}; +alias Xoroshiro1024ss = Xoroshiro{1024, XorshiftType.STAR_STAR}; +alias Xoroshiro1024pp = Xoroshiro{1024, XorshiftType.PLUS_PLUS}; + +// ================================================================= +<* + @require @in(BIT_SIZE, 128, 256, 512) : "You must use a supported state size for Xoshiro permutations." + @require $typeof(OPERATION) == XorshiftType && @in(OPERATION, PLUS, PLUS_PLUS, STAR_STAR) : "You must use a supported operation type for Xoshiro permutations." +*> +module std::math::random::xoshiro @private; + +import std::math::random @public; + +struct Xoshiro (Random) +{ + ulong[> 6>] state @if (BIT_SIZE != 128); + union @if (BIT_SIZE == 128) + { + uint[<4>] state; + ulong[<2>] long_state; + } +} + +fn void Xoshiro.set_seed(&self, char[] seed) @dynamic => random::@xorshift_seed_from_input(self, BIT_SIZE, seed); +fn void Xoshiro.set_seed_long(&self, ulong seed) => random::@xorshift_seed_from_input(self, BIT_SIZE, seed); + +<* + @require bytes.len > 0 +*> +fn void Xoshiro.next_bytes(&self, char[] bytes) @dynamic => random::@random_value_to_bytes(self.next, bytes); +fn uint128 Xoshiro.next_int128(&self) @dynamic => random::@long_to_int128(self.next_long()); +fn ulong Xoshiro.next_long(&self) @dynamic @if (BIT_SIZE != 128) => self.next(); +fn uint Xoshiro.next_int(&self) @dynamic @if (BIT_SIZE != 128) => (uint)self.next_long(); +fn ulong Xoshiro.next_long(&self) @dynamic @if (BIT_SIZE == 128) => random::@int_to_long(self.next()); +fn uint Xoshiro.next_int(&self) @dynamic @if (BIT_SIZE == 128) => self.next(); +fn ushort Xoshiro.next_short(&self) @dynamic => (ushort)self.next_long(); +fn char Xoshiro.next_byte(&self) @dynamic => (char)self.next_long(); + +// NOTE: These are defined as functions in order to keep macro expansions contained. +fn ulong Xoshiro.next(&self) @if (BIT_SIZE != 128) => xoshiro(self); +fn uint Xoshiro.next(&self) @if (BIT_SIZE == 128) => xoshiro(self); + +macro xoshiro(Xoshiro* self) @local +{ + $typeof(self.state[0]) result; + $if OPERATION == STAR_STAR: // same for all types of xoshiro + result = (self.state[1] * 5u).rotl(7) * 9u; + $else + $switch BIT_SIZE: + $case 128: + $case 256: + $switch OPERATION: + $case PLUS: result = self.state[0] + self.state[3]; + $case PLUS_PLUS: result = (self.state[0] + self.state[3]).rotl(BIT_SIZE == 128 ??? 7 : 23) + self.state[0]; + $endswitch + $case 512: + $switch OPERATION: + $case PLUS: result = self.state[0] + self.state[2]; + $case PLUS_PLUS: result = (self.state[0] + self.state[2]).rotl(17) + self.state[2]; + $endswitch + $endswitch + $endif + $switch BIT_SIZE: + $case 128: + $case 256: + sz $a = BIT_SIZE == 128 ??? 9 : 17; + sz $b = BIT_SIZE == 128 ??? 11 : 45; + var t = self.state[1] << $a; + self.state[2] ^= self.state[0]; + self.state[3] ^= self.state[1]; + self.state[1] ^= self.state[2]; + self.state[0] ^= self.state[3]; + self.state[2] ^= t; + self.state[3] = self.state[3].rotl($b); + $case 512: + ulong t = self.state[1] << 11; + self.state[2] ^= self.state[0]; + self.state[5] ^= self.state[1]; + self.state[1] ^= self.state[2]; + self.state[7] ^= self.state[3]; + self.state[3] ^= self.state[4]; + self.state[4] ^= self.state[5]; + self.state[0] ^= self.state[6]; + self.state[6] ^= self.state[7]; + self.state[6] ^= t; + self.state[7] = self.state[7].rotl(21); + $endswitch + return result; +} + + +// ================================================================= +<* + @require @in(BIT_SIZE, 64, 128, 1024) : "You must use a supported state size for Xoroshiro permutations." + @require $typeof(OPERATION) == XorshiftType && @in(OPERATION, ...XorshiftType::values) : "OPERATION must be a value of the XorshiftType enum." +*> +module std::math::random::xoroshiro @private; + +import std::math::random @public; + +struct Xoroshiro (Random) +{ + ulong[> 6>] state @if (BIT_SIZE != 64); + union @if (BIT_SIZE == 64) + { + uint[<2>] state; + ulong long_state; + } + int p @if (BIT_SIZE == 1024); +} + +fn void Xoroshiro.set_seed(&self, char[] seed) @dynamic => random::@xorshift_seed_from_input(self, BIT_SIZE, seed); +fn void Xoroshiro.set_seed_long(&self, ulong seed) => random::@xorshift_seed_from_input(self, BIT_SIZE, seed); + +<* + @require bytes.len > 0 +*> +fn void Xoroshiro.next_bytes(&self, char[] bytes) @dynamic => random::@random_value_to_bytes(self.next, bytes); + +fn uint128 Xoroshiro.next_int128(&self) @dynamic => random::@long_to_int128(self.next_long()); +fn ulong Xoroshiro.next_long(&self) @dynamic => BIT_SIZE != 64 ??? self.next() : random::@int_to_long(self.next()); +fn uint Xoroshiro.next_int(&self) @dynamic => BIT_SIZE != 64 ??? (uint)self.next_long() : self.next(); +fn ushort Xoroshiro.next_short(&self) @dynamic => (ushort)self.next_long(); +fn char Xoroshiro.next_byte(&self) @dynamic => (char)self.next_long(); + +// NOTE: These are defined as functions in order to keep macro expansions contained. +fn ulong Xoroshiro.next(&self) @if (BIT_SIZE != 64) => xoroshiro(self); +fn uint Xoroshiro.next(&self) @if (BIT_SIZE == 64) => xoroshiro(self); + +macro xoroshiro(Xoroshiro* self) @local +{ + uint a, b, c; + $switch BIT_SIZE: + $case 64: + uint result; + $switch OPERATION: + $case STAR: result = self.state[0] * 0x9e3779bb; + $case STAR_STAR: result = (self.state[0] * 0x9e3779bb).rotl(5) * 5u; + $default: $error "Invalid bit-size and XorshiftType combination."; + $endswitch + uint s1 = self.state[1] ^ self.state[0]; + self.state[0] = self.state[0].rotl(26) ^ s1 ^ (s1 << 9); + self.state[1] = s1.rotl(13); + return result; + $case 128: + ulong result; + ulong s1 = self.state[1] ^ self.state[0]; + $switch OPERATION: + $case PLUS: + result = self.state[0] + self.state[1]; + a = 24; b = 16; c = 37; + $case PLUS_PLUS: + result = (self.state[0] + self.state[1]).rotl(17) + self.state[0]; + a = 49; b = 21; c = 28; + $case STAR_STAR: + result = (self.state[0] * 5u).rotl(7u) * 9u; + a = 24; b = 16; c = 37; + $default: $error "Invalid bit-size and XorshiftType combination."; + $endswitch + self.state[0] = self.state[0].rotl(a) ^ s1 ^ (s1 << b); + self.state[1] = s1.rotl(c); + return result; + $case 1024: + ulong result; + int q = self.p; + self.p = (self.p + 1) & 0xf; + ulong s0 = self.state[self.p]; + ulong s15 = self.state[q]; + $switch OPERATION: + $case PLUS_PLUS: + result = (s0 + s15).rotl(23) + s15; + $case STAR: + result = s0 * 0x9e3779b97f4a7c13; + $case STAR_STAR: + result = (s0 * 5u).rotl(7u) * 9u; + $default: $error "Invalid bit-size and XorshiftType combination."; + $endswitch + s15 ^= s0; + self.state[q] = s0.rotl(25u) ^ s15 ^ (s15 << 27); + self.state[self.p] = s15.rotl(36u); + return result; + $endswitch + unreachable(); +} diff --git a/test/stdlib/src/math/random/splitmix64.c3 b/test/stdlib/src/math/random/splitmix64.c3 new file mode 100644 index 0000000..22f2f3c --- /dev/null +++ b/test/stdlib/src/math/random/splitmix64.c3 @@ -0,0 +1,48 @@ +// Copyright (c) 2026 Zack Puhl . All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +// +// A very fast, painfully simple PRNG that should only be used for less demanding PRNG tasks +// like deriving seeds for more complex PRNGs. +// See: https://rosettacode.org/wiki/Pseudo-random_numbers/Splitmix64 +// +module std::math::random; + +typedef SplitMix64 (Random) @constinit = ulong; + +const ulong GOLDEN_RATIO_64 = 0x9e3779b97f4a7c15UL; +const ulong SILVER_RATIO_64 = 0x6a09e667f3bcc909UL; + +fn void SplitMix64.set_seed(&self, char[] seed) @dynamic +{ + *self = (SplitMix64)(random::make_seed(ulong, seed)); +} + +<* Directly set the underlying `ulong` so as to manually initialize or seed the PRNG. *> +fn void SplitMix64.set(&self, ulong state) => *self = (SplitMix64)state; + +fn ulong SplitMix64.next_long(&self) @dynamic +{ + ulong z = (*(ulong*)self += GOLDEN_RATIO_64); + return mix_stafford13(z); +} + +<* + Magic numbers sourced from our friends at openjdk - where a lot of this Xorshift stuff lives. + See: https://github.com/openjdk/jdk/blob/814b7f5d2a76288b6a8d6510a10c0241fdb727d0/src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java#L811 +*> +fn ulong mix_stafford13(ulong z) +{ + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9UL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebUL; + return z ^ (z >> 31); +} + +<* + @require bytes.len > 0 +*> +fn void SplitMix64.next_bytes(&self, char[] bytes) @dynamic => @random_value_to_bytes(self.next_long, bytes); +fn uint128 SplitMix64.next_int128(&self) @dynamic => @long_to_int128(self.next_long()); +fn uint SplitMix64.next_int(&self) @dynamic => (uint)self.next_long(); +fn ushort SplitMix64.next_short(&self) @dynamic => (ushort)self.next_long(); +fn char SplitMix64.next_byte(&self) @dynamic => (char)self.next_long(); diff --git a/test/stdlib/src/math/shapes.c3 b/test/stdlib/src/math/shapes.c3 new file mode 100644 index 0000000..37c5cf8 --- /dev/null +++ b/test/stdlib/src/math/shapes.c3 @@ -0,0 +1,239 @@ +module std::math; + +alias Rect = Rectangle {float}; + +<* + @require types::is_float(Num) || types::is_int(Num) : "Only integer or floating point rectangles are supported" +*> +module std::math::rectangle ; + +union Rectangle +{ + struct + { + Num x, y, width, height; + } + Num[<4>] v; + struct + { + Num[<2>] origin; + Num[<2>] size; + } +} + +// Accessors + +<* + Returns the minimum corner (same as origin) + + @return "The minimum corner" +*> +fn Num[<2>] Rectangle.min(self) @inline +{ + return self.origin; +} + +<* + Returns the maximum corner (origin + size) + + @return "the maximum corner" +*> +fn Num[<2>] Rectangle.max(self) @inline +{ + return self.origin + self.size; +} + +<* + Returns the center point of the rectangle. + + @return "The center point" +*> +fn Num[<2>] Rectangle.center(self) +{ + return self.origin + self.size / 2; +} + +<* + Returns the area of the rectangle. + + @return "The area" +*> +fn Num Rectangle.area(self) +{ + return self.size.product(); +} + +<* + Return error if the rectangle is empty. + + @require self.size.min() >= 0 : "Invalid rectangle with negative size" + @return "True is the rectangle is empty" +*> +fn bool Rectangle.is_empty(self) +{ + return self.size.min() == 0; +} + +// Spatial queries + +<* + Check if a point is contained in a rectangle + + @return "true if the point is inside or on the border of the rectangle" +*> +fn bool Rectangle.contains_point(self, Num[<2>] point) +{ + return self.origin.comp_le(point).and() & (self.origin + self.size).comp_ge(point).and(); +} + + +<* + Checks if the rectangle fully contains another rectangle. + + @return "true if the rectangle fully contains the other rectangle" +*> +fn bool Rectangle.contains_rect(self, Rectangle other) +{ + return self.contains_point(other.origin) && self.contains_point(other.max()); +} + +<* + Test if rectangles overlap. + + @param other : "The rectangle to compare with" + @return "true if the rectangles overlap" +*> +fn bool Rectangle.intersects(self, Rectangle other) +{ + Num[<4>] a = self.v; + Num[<4>] b = other.v; + return a.x + a.z > b.x + && a.x < b.x + b.z + && a.y + a.w > b.y + && a.y < b.y + b.w; +} + + +// Geometry operations + +<* + Merge two rectangles into a rectangle enough to contain both rectangles. + + @return "the smallest rectangle containing both rectangles" +*> +fn Rectangle Rectangle.merge(self, Rectangle other) +{ + Num[<2>] min = math::min(self.min(), other.min()); + Num[<2>] max = math::max(self.max(), other.max()); + return { .origin = min, .size = max - min }; +} + +<* + Return the rectangle intersecting both rectangles. + + @param other : "The other rectangle to form the intersection with" + + @return? math::NO_INTERSECTION : "If the rectangles do not intersect" + @return "The resulting intersection between the two" +*> +fn Rectangle? Rectangle.intersection(self, Rectangle other) +{ + Num[<2>] min = math::max(self.min(), other.min()); + Num[<2>] max = math::min(self.max(), other.max()); + + if (min.x >= max.x || min.y >= max.y) return math::NO_INTERSECTION~; + + return { .origin = min, .size = max - min }; +} + +// Modification + +<* + Expands the rectangle to have integral boundaries. + @return "the resulting rectangle" +*> +fn Rectangle Rectangle.integral_outer(self) @if(types::is_float(Num)) +{ + Num[<2>] min = math::floor(self.origin); + Num[<2>] max = math::ceil(self.max()); + + return { .origin = min, .size = max - min }; +} + +<* + Shrinks the rectangle to have integral boundaries. + @return "the resulting rectangle" +*> +fn Rectangle Rectangle.integral_inner(self) @if(types::is_float(Num)) +{ + Num[<2>] min = math::ceil(self.origin); + Num[<2>] max = math::floor(self.max()); + + return { .origin = min, .size = math::max(max - min, (Num[<2>])0) }; +} + +<* + Shrinks the rectangle by amount from all sides. + + @require amount >= 0 : "Expected a positive or zero amount" + @require (self.size - amount * 2).min() >= 0 : "Inset must result in a zero or positive size" + @return "the resulting rectangle" +*> +fn Rectangle Rectangle.inset(self, Num amount) +{ + return { .origin = self.origin + amount, .size = self.size - amount * 2}; +} + +<* + Shrinks the rectangle by amount from all sides, clamping to zero size if necessary. + + @require amount >= 0 : "Expected a positive or zero amount" + @return "the resulting rectangle" +*> +fn Rectangle Rectangle.inset_clamped(self, Num amount) +{ + Num[<2>] new_size = math::max(self.size - amount * 2, (Num[<2>])0); + Num[<2>] decrease = self.size - new_size; + return { .origin = self.origin + decrease / 2, .size = new_size}; +} + +<* + Expand the rectangle by the amount in all directions. + + @require amount >= 0 : "Expected a positive or zero amount" + @require amount * 2 + self.width >= 0 : "The amount would make the width negative" + @require amount * 2 + self.height >= 0 : "The amount would make the height negative" + @ensure return.contains_rect(self) + + @return "the resulting rectangle" +*> +fn Rectangle Rectangle.expand(self, Num amount) +{ + return { .origin = self.origin - amount, .size = self.size + amount * 2}; +} + +<* + Expands the rectangle minimally to include a point. + + @param point : "The point to expand the rectangle with" + @return "the resulting rectangle" + + @ensure return.contains_point(point) +*> +fn Rectangle Rectangle.merge_point(self, Num[<2>] point) +{ + Num[<2>] min = math::min(self.min(), point); + Num[<2>] max = math::max(self.max(), point); + return { .origin = min, .size = max - min }; +} + +<* + Clamps a point to lie inside a rectangle. + + @return "the resulting point" + @ensure self.contains_point(return) +*> +fn Num[<2>] Rectangle.clamp_point(self, Num[<2>] point) +{ + return math::min(self.max(), math::max(self.min(), point)); +} \ No newline at end of file diff --git a/test/stdlib/src/math/uuid.c3 b/test/stdlib/src/math/uuid.c3 index b05279d..14148bb 100644 --- a/test/stdlib/src/math/uuid.c3 +++ b/test/stdlib/src/math/uuid.c3 @@ -25,7 +25,7 @@ fn Uuid generate_from_random(Random random) return uuid; } -fn usz? Uuid.to_format(&self, Formatter* formatter) @dynamic +fn sz? Uuid.to_format(&self, Formatter* formatter) @dynamic { return formatter.printf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", (*self)[0], (*self)[1], (*self)[2], (*self)[3], diff --git a/test/stdlib/src/math/vector.c3 b/test/stdlib/src/math/vector.c3 index add4678..8ad5e73 100644 --- a/test/stdlib/src/math/vector.c3 +++ b/test/stdlib/src/math/vector.c3 @@ -1,15 +1,18 @@ // Vector supplemental methods - module std::math::vector; -import std::math; +import std::math @public; + +alias Vec2 @weak = float[<2>]; +alias Vec3 @weak = float[<3>]; +alias Vec4 @weak = float[<4>]; <* - @require $Type.kindof == VECTOR &&& $kindof(($Type){}[0]) == BOOL : "Expected a bool vector" - @require $kindof(mask).is_int() : "Expected an integer mask" + @require $Type::kind == VECTOR &&& @kindof(($Type){}[0]) == BOOL : "Expected a bool vector" + @require @kindof(mask).is_int() : "Expected an integer mask" *> macro bool[<*>] mask_from_int($Type, mask) { - return $$int_to_mask(mask, $Type.len); + return $$int_to_mask(mask, $Type::len); } macro bool[<*>].mask_to_int(self) @@ -17,12 +20,13 @@ macro bool[<*>].mask_to_int(self) return $$mask_to_int(self); } -macro double double[<*>].sq_magnitude(self) => self.dot(self); -macro float float[<*>].sq_magnitude(self) => self.dot(self); +fn float[<3>] cubic_hermitef(float[<3>] v1, float[<3>] tangent1, float[<3>] v2, float[<3>] tangent2, float amount) => cubic_hermite3(v1, tangent1, v2, tangent2, amount); +fn void orthonormalizef(float[<3>]* v1, float[<3>]* v2) => ortho_normalize3(v1, v2); -macro double double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).sq_magnitude(); -macro float float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).sq_magnitude(); +fn double[<3>] cubic_hermite(double[<3>] v1, double[<3>] tangent1, double[<3>] v2, double[<3>] tangent2, double amount) => cubic_hermite3(v1, tangent1, v2, tangent2, amount); +fn void orthonormalize(double[<3>]* v1, double[<3>]* v2) => ortho_normalize3(v1, v2); +fn float float[<2>].cross(self, float[<2>] v2) => cross2(self, v2); macro float[<2>] float[<2>].transform(self, Matrix4f mat) => transform2(self, mat); macro float[<2>] float[<2>].rotate(self, float angle) => rotate(self, angle); macro float[<2>] float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x); @@ -30,192 +34,303 @@ macro float[<2>] float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x macro double[<2>] double[<2>].transform(self, Matrix4 mat) => transform2(self, mat); macro double[<2>] double[<2>].rotate(self, double angle) => rotate(self, angle); macro double[<2>] double[<2>].angle(self, double[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x); +fn double double[<2>].cross(self, double[<2>] v2) => cross2(self, v2); -macro float[<*>] float[<*>].clamp_mag(self, float min, float max) => clamp_magnitude(self, min, max); -macro double[<*>] double[<*>].clamp_mag(self, double min, double max) => clamp_magnitude(self, min, max); - -macro float[<*>] float[<*>].towards(self, float[<*>] target, float max_distance) => towards(self, target, max_distance); -macro double[<*>] double[<*>].towards(self, double[<*>] target, double max_distance) => towards(self, target, max_distance); - +fn float float[<3>].angle(self, float[<3>] v2) => angle3(self, v2); fn float[<3>] float[<3>].cross(self, float[<3>] v2) => cross3(self, v2); -fn double[<3>] double[<3>].cross(self, double[<3>] v2) => cross3(self, v2); - fn float[<3>] float[<3>].perpendicular(self) => perpendicular3(self); -fn double[<3>] double[<3>].perpendicular(self) => perpendicular3(self); - -fn float[<3>] float[<3>].barycenter(self, float[<3>] a, float[<3>] b, float[<3>] c) => barycenter3(self, a, b, c); -fn double[<3>] double[<3>].barycenter(self, double[<3>] a, double[<3>] b, double[<3>] c) => barycenter3(self, a, b, c); - +fn float[<3>] float[<3>].barycentric(self, float[<3>] a, float[<3>] b, float[<3>] c) => barycentric3(self, a, b, c); fn float[<3>] float[<3>].transform(self, Matrix4f mat) => transform3(self, mat); -fn double[<3>] double[<3>].transform(self, Matrix4 mat) => transform3(self, mat); +fn float[<3>] float[<3>].refract(self, float[<3>] n, float r) => refract3(self, n, r); +fn float[<3>] float[<3>].rotate_quat(self, Quaternionf q) => q * self; +fn float[<3>] float[<3>].rotate_axis(self, float[<3>] axis, float angle) => rotate_axis_angle(self, axis, angle); +fn float[<3>] float[<3>].rejection(v1, float[<3>] v2) => rejection3(v1, v2); +fn float[<3>] float[<3>].project(self, Matrix4f model, Matrix4f projection, float[<4>] viewport) => project3(self, model, projection, viewport); +fn float[<3>] float[<3>].unproject(self, Matrix4f model, Matrix4f projection, float[<4>] viewport) => unproject3(self, model, projection, viewport); -fn float float[<3>].angle(self, float[<3>] v2) => angle3(self, v2); fn double double[<3>].angle(self, double[<3>] v2) => angle3(self, v2); - -fn float[<3>] float[<3>].refract(self, float[<3>] n, float r) => refract3(self, n, r); +fn double[<3>] double[<3>].cross(self, double[<3>] v2) => cross3(self, v2); +fn double[<3>] double[<3>].perpendicular(self) => perpendicular3(self); +fn double[<3>] double[<3>].barycentric(self, double[<3>] a, double[<3>] b, double[<3>] c) => barycentric3(self, a, b, c); +fn double[<3>] double[<3>].transform(self, Matrix4 mat) => transform3(self, mat); fn double[<3>] double[<3>].refract(self, double[<3>] n, double r) => refract3(self, n, r); - -fn float[<3>] float[<3>].rotate_quat(self, Quaternionf q) => q * self; fn double[<3>] double[<3>].rotate_quat(self, Quaternion q) => q * self; - -fn float[<3>] float[<3>].rotate_axis(self, float[<3>] axis, float angle) => rotate_axis_angle(self, axis, angle); fn double[<3>] double[<3>].rotate_axis(self, double[<3>] axis, double angle) => rotate_axis_angle(self, axis, angle); +fn double[<3>] double[<3>].rejection(v1, double[<3>] v2) => rejection3(v1, v2); +fn double[<3>] double[<3>].project(self, Matrix4 model, Matrix4 projection, double[<4>] viewport) => project3(self, model, projection, viewport); +fn double[<3>] double[<3>].unproject(self, Matrix4 model, Matrix4 projection, double[<4>] viewport) => unproject3(self, model, projection, viewport); + +macro float float[<*>].sum(float[<*>] x, float start = 0.0) => $$reduce_fadd(x, start); +macro float float[<*>].product(float[<*>] x, float start = 1.0) => $$reduce_fmul(x, start); +macro float float[<*>].max(float[<*>] x) => $$reduce_max(x); +macro float float[<*>].min(float[<*>] x) => $$reduce_min(x); +macro float[<*>] float[<*>].ceil(float[<*>] x) => $$ceil(x); +macro float[<*>] float[<*>].clamp(float[<*>] x, float[<*>] lower, float[<*>] upper) => $$max(lower, $$min(x, upper)); +macro float[<*>] float[<*>].copysign(float[<*>] mag, float[<*>] sgn) => $$copysign(mag, sgn); +macro float[<*>] float[<*>].fma(float[<*>] a, float[<*>] b, float[<*>] c) => $$fma(a, b, c); +macro float[<*>] float[<*>].floor(float[<*>] x) => $$floor(x); +macro float[<*>] float[<*>].nearbyint(float[<*>] x) => $$nearbyint(x); +macro float[<*>] float[<*>].pow(float[<*>] x, exp) => pow(x, exp); +macro float[<*>] float[<*>].rint(float[<*>] x) => $$rint(x); +macro float[<*>] float[<*>].round(float[<*>] x) => $$round(x); +macro float[<*>] float[<*>].roundeven(float[<*>] x) => $$roundeven(x); +macro float[<*>] float[<*>].trunc(float[<*>] x) => $$trunc(x); +macro float float[<*>].dot(float[<*>] x, float[<*>] y) => (x * y).sum(); +macro float float[<*>].length(float[<*>] x) => $$sqrt(x.dot(x)); +macro float float[<*>].length_sq(self) => self.dot(self); +macro float float[<*>].distance(float[<*>] x, float[<*>] y) => (x - y).length(); +macro float float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).length_sq(); +macro float[<*>] float[<*>].normalize(float[<*>] x) => normalize(x); +macro float[<*>] float[<*>].lerp(float[<*>] x, float[<*>] y, float amount) => math::lerp(x, y, amount); +macro float[<*>] float[<*>].reflect(float[<*>] x, float[<*>] y) => reflect(x, y); +macro float[<*>] float[<*>].clamp_length(self, float min, float max) => clamp_length{$typeof(self)}(self, min, max); +macro float[<*>] float[<*>].move_towards(self, float[<*>] target, float max_distance) => towards{$typeof(self)}(self, target, max_distance); +macro bool float[<*>].equals(float[<*>] x, float[<*>] y) => equals(x, y); +macro bool float[<*>].not_equals(float[<*>] x, float[<*>] y) => not_equals(x, y); +macro bool float[<*>].is_normalized(float[<*>] x) => math::is_approx(x.length_sq(), 1.0, math::FLOAT_VECTOR_EPSILON); +macro bool[<*>] float[<*>].comp_lt(float[<*>] x, float[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] float[<*>].comp_le(float[<*>] x, float[<*>] y) => $$veccomple(x, y); +macro bool[<*>] float[<*>].comp_eq(float[<*>] x, float[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] float[<*>].comp_gt(float[<*>] x, float[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] float[<*>].comp_ge(float[<*>] x, float[<*>] y) => $$veccompge(x, y); +macro bool[<*>] float[<*>].comp_ne(float[<*>] x, float[<*>] y) => $$veccompne(x, y); + +macro double double[<*>].sum(double[<*>] x, double start = 0.0) => $$reduce_fadd(x, start); +macro double double[<*>].product(double[<*>] x, double start = 1.0) => $$reduce_fmul(x, start); +macro double double[<*>].max(double[<*>] x) => $$reduce_max(x); +macro double double[<*>].min(double[<*>] x) => $$reduce_min(x); +macro double[<*>] double[<*>].ceil(double[<*>] x) => $$ceil(x); +macro double[<*>] double[<*>].clamp(double[<*>] x, double[<*>] lower, double[<*>] upper) => $$max(lower, $$min(x, upper)); +macro double[<*>] double[<*>].copysign(double[<*>] mag, double[<*>] sgn) => $$copysign(mag, sgn); +macro double[<*>] double[<*>].floor(double[<*>] x) => $$floor(x); +macro double[<*>] double[<*>].fma(double[<*>] a, double[<*>] b, double[<*>] c) => $$fma(a, b, c); +macro double[<*>] double[<*>].nearbyint(double[<*>] x) => $$nearbyint(x); +macro double[<*>] double[<*>].pow(double[<*>] x, exp) => pow(x, exp); +macro double[<*>] double[<*>].rint(double[<*>] x) => $$rint(x); +macro double[<*>] double[<*>].round(double[<*>] x) => $$round(x); +macro double[<*>] double[<*>].roundeven(double[<*>] x) => $$roundeven(x); +macro double[<*>] double[<*>].trunc(double[<*>] x) => $$trunc(x); +macro double double[<*>].dot(double[<*>] x, double[<*>] y) => (x * y).sum(); +macro double double[<*>].length(double[<*>] x) => $$sqrt(x.dot(x)); +macro double double[<*>].length_sq(double[<*>] x) => x.dot(x); +macro double double[<*>].distance(double[<*>] x, double[<*>] y) => (x - y).length(); +macro double double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).length_sq(); +macro double[<*>] double[<*>].clamp_length(self, double min, double max) => clamp_length{$typeof(self)}(self, min, max); +macro double[<*>] double[<*>].move_towards(self, double[<*>] target, double max_distance) => towards{$typeof(self)}(self, target, max_distance); +macro double[<*>] double[<*>].normalize(double[<*>] x) => normalize(x); +macro double[<*>] double[<*>].reflect(double[<*>] x, double[<*>] y) => reflect(x, y); +macro double[<*>] double[<*>].lerp(double[<*>] x, double[<*>] y, double amount) => math::lerp(x, y, amount); +macro bool double[<*>].equals(double[<*>] x, double[<*>] y) => equals(x, y); +macro bool double[<*>].not_equals(double[<*>] x, double[<*>] y) => not_equals(x, y); +macro bool double[<*>].is_normalized(double[<*>] x) => math::is_approx(x.length_sq(), 1.0, math::DOUBLE_VECTOR_EPSILON); +macro bool[<*>] double[<*>].comp_lt(double[<*>] x, double[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] double[<*>].comp_le(double[<*>] x, double[<*>] y) => $$veccomple(x, y); +macro bool[<*>] double[<*>].comp_eq(double[<*>] x, double[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] double[<*>].comp_gt(double[<*>] x, double[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] double[<*>].comp_ge(double[<*>] x, double[<*>] y) => $$veccompge(x, y); +macro bool[<*>] double[<*>].comp_ne(double[<*>] x, double[<*>] y) => $$veccompne(x, y); + +macro bool[<*>] ichar[<*>].comp_lt(ichar[<*>] x, ichar[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] ichar[<*>].comp_le(ichar[<*>] x, ichar[<*>] y) => $$veccomple(x, y); +macro bool[<*>] ichar[<*>].comp_eq(ichar[<*>] x, ichar[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] ichar[<*>].comp_gt(ichar[<*>] x, ichar[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] ichar[<*>].comp_ge(ichar[<*>] x, ichar[<*>] y) => $$veccompge(x, y); +macro bool[<*>] ichar[<*>].comp_ne(ichar[<*>] x, ichar[<*>] y) => $$veccompne(x, y); +macro ichar ichar[<*>].sum(ichar[<*>] x) => $$reduce_add(x); +macro ichar ichar[<*>].product(ichar[<*>] x) => $$reduce_mul(x); +macro ichar ichar[<*>].and(ichar[<*>] x) => $$reduce_and(x); +macro ichar ichar[<*>].or(ichar[<*>] x) => $$reduce_or(x); +macro ichar ichar[<*>].xor(ichar[<*>] x) => $$reduce_xor(x); +macro ichar ichar[<*>].max(ichar[<*>] x) => $$reduce_max(x); +macro ichar ichar[<*>].min(ichar[<*>] x) => $$reduce_min(x); +macro ichar ichar[<*>].dot(ichar[<*>] x, ichar[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro ichar[<*>] ichar[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] short[<*>].comp_lt(short[<*>] x, short[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] short[<*>].comp_le(short[<*>] x, short[<*>] y) => $$veccomple(x, y); +macro bool[<*>] short[<*>].comp_eq(short[<*>] x, short[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] short[<*>].comp_gt(short[<*>] x, short[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] short[<*>].comp_ge(short[<*>] x, short[<*>] y) => $$veccompge(x, y); +macro bool[<*>] short[<*>].comp_ne(short[<*>] x, short[<*>] y) => $$veccompne(x, y); +macro short short[<*>].sum(short[<*>] x) => $$reduce_add(x); +macro short short[<*>].product(short[<*>] x) => $$reduce_mul(x); +macro short short[<*>].and(short[<*>] x) => $$reduce_and(x); +macro short short[<*>].or(short[<*>] x) => $$reduce_or(x); +macro short short[<*>].xor(short[<*>] x) => $$reduce_xor(x); +macro short short[<*>].max(short[<*>] x) => $$reduce_max(x); +macro short short[<*>].min(short[<*>] x) => $$reduce_min(x); +macro short short[<*>].dot(short[<*>] x, short[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro short[<*>] short[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] int[<*>].comp_lt(int[<*>] x, int[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] int[<*>].comp_le(int[<*>] x, int[<*>] y) => $$veccomple(x, y); +macro bool[<*>] int[<*>].comp_eq(int[<*>] x, int[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] int[<*>].comp_gt(int[<*>] x, int[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] int[<*>].comp_ge(int[<*>] x, int[<*>] y) => $$veccompge(x, y); +macro bool[<*>] int[<*>].comp_ne(int[<*>] x, int[<*>] y) => $$veccompne(x, y); +macro int int[<*>].sum(int[<*>] x) => $$reduce_add(x); +macro int int[<*>].product(int[<*>] x) => $$reduce_mul(x); +macro int int[<*>].and(int[<*>] x) => $$reduce_and(x); +macro int int[<*>].or(int[<*>] x) => $$reduce_or(x); +macro int int[<*>].xor(int[<*>] x) => $$reduce_xor(x); +macro int int[<*>].max(int[<*>] x) => $$reduce_max(x); +macro int int[<*>].min(int[<*>] x) => $$reduce_min(x); +macro int int[<*>].dot(int[<*>] x, int[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro int[<*>] int[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] long[<*>].comp_lt(long[<*>] x, long[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] long[<*>].comp_le(long[<*>] x, long[<*>] y) => $$veccomple(x, y); +macro bool[<*>] long[<*>].comp_eq(long[<*>] x, long[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] long[<*>].comp_gt(long[<*>] x, long[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] long[<*>].comp_ge(long[<*>] x, long[<*>] y) => $$veccompge(x, y); +macro bool[<*>] long[<*>].comp_ne(long[<*>] x, long[<*>] y) => $$veccompne(x, y); +macro long long[<*>].sum(long[<*>] x) => $$reduce_add(x); +macro long long[<*>].product(long[<*>] x) => $$reduce_mul(x); +macro long long[<*>].and(long[<*>] x) => $$reduce_and(x); +macro long long[<*>].or(long[<*>] x) => $$reduce_or(x); +macro long long[<*>].xor(long[<*>] x) => $$reduce_xor(x); +macro long long[<*>].max(long[<*>] x) => $$reduce_max(x); +macro long long[<*>].min(long[<*>] x) => $$reduce_min(x); +macro long long[<*>].dot(long[<*>] x, long[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro long[<*>] long[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] int128[<*>].comp_lt(int128[<*>] x, int128[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] int128[<*>].comp_le(int128[<*>] x, int128[<*>] y) => $$veccomple(x, y); +macro bool[<*>] int128[<*>].comp_eq(int128[<*>] x, int128[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] int128[<*>].comp_gt(int128[<*>] x, int128[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] int128[<*>].comp_ge(int128[<*>] x, int128[<*>] y) => $$veccompge(x, y); +macro bool[<*>] int128[<*>].comp_ne(int128[<*>] x, int128[<*>] y) => $$veccompne(x, y); +macro int128 int128[<*>].sum(int128[<*>] x) => $$reduce_add(x); +macro int128 int128[<*>].product(int128[<*>] x) => $$reduce_mul(x); +macro int128 int128[<*>].and(int128[<*>] x) => $$reduce_and(x); +macro int128 int128[<*>].or(int128[<*>] x) => $$reduce_or(x); +macro int128 int128[<*>].xor(int128[<*>] x) => $$reduce_xor(x); +macro int128 int128[<*>].max(int128[<*>] x) => $$reduce_max(x); +macro int128 int128[<*>].min(int128[<*>] x) => $$reduce_min(x); +macro int128 int128[<*>].dot(int128[<*>] x, int128[<*>] y) => (x * y).sum(); + +macro bool[<*>] bool[<*>].comp_lt(bool[<*>] x, bool[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] bool[<*>].comp_le(bool[<*>] x, bool[<*>] y) => $$veccomple(x, y); +macro bool[<*>] bool[<*>].comp_eq(bool[<*>] x, bool[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] bool[<*>].comp_gt(bool[<*>] x, bool[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] bool[<*>].comp_ge(bool[<*>] x, bool[<*>] y) => $$veccompge(x, y); +macro bool[<*>] bool[<*>].comp_ne(bool[<*>] x, bool[<*>] y) => $$veccompne(x, y); +macro bool bool[<*>].sum(bool[<*>] x) => $$reduce_add(x); +macro bool bool[<*>].product(bool[<*>] x) => $$reduce_mul(x); +macro bool bool[<*>].and(bool[<*>] x) => $$reduce_and(x); +macro bool bool[<*>].or(bool[<*>] x) => $$reduce_or(x); +macro bool bool[<*>].xor(bool[<*>] x) => $$reduce_xor(x); +macro bool bool[<*>].max(bool[<*>] x) => $$reduce_max(x); +macro bool bool[<*>].min(bool[<*>] x) => $$reduce_min(x); + +macro bool[<*>] char[<*>].comp_lt(char[<*>] x, char[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] char[<*>].comp_le(char[<*>] x, char[<*>] y) => $$veccomple(x, y); +macro bool[<*>] char[<*>].comp_eq(char[<*>] x, char[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] char[<*>].comp_gt(char[<*>] x, char[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] char[<*>].comp_ge(char[<*>] x, char[<*>] y) => $$veccompge(x, y); +macro bool[<*>] char[<*>].comp_ne(char[<*>] x, char[<*>] y) => $$veccompne(x, y); +macro char char[<*>].sum(char[<*>] x) => $$reduce_add(x); +macro char char[<*>].product(char[<*>] x) => $$reduce_mul(x); +macro char char[<*>].and(char[<*>] x) => $$reduce_and(x); +macro char char[<*>].or(char[<*>] x) => $$reduce_or(x); +macro char char[<*>].xor(char[<*>] x) => $$reduce_xor(x); +macro char char[<*>].max(char[<*>] x) => $$reduce_max(x); +macro char char[<*>].min(char[<*>] x) => $$reduce_min(x); +macro char char[<*>].dot(char[<*>] x, char[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro char[<*>] char[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] ushort[<*>].comp_lt(ushort[<*>] x, ushort[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] ushort[<*>].comp_le(ushort[<*>] x, ushort[<*>] y) => $$veccomple(x, y); +macro bool[<*>] ushort[<*>].comp_eq(ushort[<*>] x, ushort[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] ushort[<*>].comp_gt(ushort[<*>] x, ushort[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] ushort[<*>].comp_ge(ushort[<*>] x, ushort[<*>] y) => $$veccompge(x, y); +macro bool[<*>] ushort[<*>].comp_ne(ushort[<*>] x, ushort[<*>] y) => $$veccompne(x, y); +macro ushort ushort[<*>].sum(ushort[<*>] x) => $$reduce_add(x); +macro ushort ushort[<*>].product(ushort[<*>] x) => $$reduce_mul(x); +macro ushort ushort[<*>].and(ushort[<*>] x) => $$reduce_and(x); +macro ushort ushort[<*>].or(ushort[<*>] x) => $$reduce_or(x); +macro ushort ushort[<*>].xor(ushort[<*>] x) => $$reduce_xor(x); +macro ushort ushort[<*>].max(ushort[<*>] x) => $$reduce_max(x); +macro ushort ushort[<*>].min(ushort[<*>] x) => $$reduce_min(x); +macro ushort ushort[<*>].dot(ushort[<*>] x, ushort[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro ushort[<*>] ushort[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] uint[<*>].comp_lt(uint[<*>] x, uint[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] uint[<*>].comp_le(uint[<*>] x, uint[<*>] y) => $$veccomple(x, y); +macro bool[<*>] uint[<*>].comp_eq(uint[<*>] x, uint[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] uint[<*>].comp_gt(uint[<*>] x, uint[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] uint[<*>].comp_ge(uint[<*>] x, uint[<*>] y) => $$veccompge(x, y); +macro bool[<*>] uint[<*>].comp_ne(uint[<*>] x, uint[<*>] y) => $$veccompne(x, y); +macro uint uint[<*>].sum(uint[<*>] x) => $$reduce_add(x); +macro uint uint[<*>].product(uint[<*>] x) => $$reduce_mul(x); +macro uint uint[<*>].and(uint[<*>] x) => $$reduce_and(x); +macro uint uint[<*>].or(uint[<*>] x) => $$reduce_or(x); +macro uint uint[<*>].xor(uint[<*>] x) => $$reduce_xor(x); +macro uint uint[<*>].max(uint[<*>] x) => $$reduce_max(x); +macro uint uint[<*>].min(uint[<*>] x) => $$reduce_min(x); +macro uint uint[<*>].dot(uint[<*>] x, uint[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro uint[<*>] uint[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] ulong[<*>].comp_lt(ulong[<*>] x, ulong[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] ulong[<*>].comp_le(ulong[<*>] x, ulong[<*>] y) => $$veccomple(x, y); +macro bool[<*>] ulong[<*>].comp_eq(ulong[<*>] x, ulong[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] ulong[<*>].comp_gt(ulong[<*>] x, ulong[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] ulong[<*>].comp_ge(ulong[<*>] x, ulong[<*>] y) => $$veccompge(x, y); +macro bool[<*>] ulong[<*>].comp_ne(ulong[<*>] x, ulong[<*>] y) => $$veccompne(x, y); +macro ulong ulong[<*>].sum(ulong[<*>] x) => $$reduce_add(x); +macro ulong ulong[<*>].product(ulong[<*>] x) => $$reduce_mul(x); +macro ulong ulong[<*>].and(ulong[<*>] x) => $$reduce_and(x); +macro ulong ulong[<*>].or(ulong[<*>] x) => $$reduce_or(x); +macro ulong ulong[<*>].xor(ulong[<*>] x) => $$reduce_xor(x); +macro ulong ulong[<*>].max(ulong[<*>] x) => $$reduce_max(x); +macro ulong ulong[<*>].min(ulong[<*>] x) => $$reduce_min(x); +macro ulong ulong[<*>].dot(ulong[<*>] x, ulong[<*>] y) => (x * y).sum(); +<* + @require @is_same_vector_or_scalar(self, mul) : `mul must be a vector of the same type as self, or be an integer scalar` + @require @is_same_vector_or_scalar(self, div) : `div must be a vector of the same type as self, or be an integer scalar` +*> +macro ulong[<*>] ulong[<*>].muldiv(self, mul, div) => math::mul_div_helper(self, mul, div); + +macro bool[<*>] uint128[<*>].comp_lt(uint128[<*>] x, uint128[<*>] y) => $$veccomplt(x, y); +macro bool[<*>] uint128[<*>].comp_le(uint128[<*>] x, uint128[<*>] y) => $$veccomple(x, y); +macro bool[<*>] uint128[<*>].comp_eq(uint128[<*>] x, uint128[<*>] y) => $$veccompeq(x, y); +macro bool[<*>] uint128[<*>].comp_gt(uint128[<*>] x, uint128[<*>] y) => $$veccompgt(x, y); +macro bool[<*>] uint128[<*>].comp_ge(uint128[<*>] x, uint128[<*>] y) => $$veccompge(x, y); +macro bool[<*>] uint128[<*>].comp_ne(uint128[<*>] x, uint128[<*>] y) => $$veccompne(x, y); +macro uint128 uint128[<*>].sum(uint128[<*>] x) => $$reduce_add(x); +macro uint128 uint128[<*>].product(uint128[<*>] x) => $$reduce_mul(x); +macro uint128 uint128[<*>].and(uint128[<*>] x) => $$reduce_and(x); +macro uint128 uint128[<*>].or(uint128[<*>] x) => $$reduce_or(x); +macro uint128 uint128[<*>].xor(uint128[<*>] x) => $$reduce_xor(x); +macro uint128 uint128[<*>].max(uint128[<*>] x) => $$reduce_max(x); +macro uint128 uint128[<*>].min(uint128[<*>] x) => $$reduce_min(x); +macro uint128 uint128[<*>].dot(uint128[<*>] x, uint128[<*>] y) => (x * y).sum(); -fn float[<3>] float[<3>].unproject(self, Matrix4f projection, Matrix4f view) => unproject3(self, projection, view); -fn double[<3>] double[<3>].unproject(self, Matrix4 projection, Matrix4 view) => unproject3(self, projection, view); - -fn void ortho_normalize(float[<3>]* v1, float[<3>]* v2) => ortho_normalize3(v1, v2); -fn void ortho_normalized(double[<3>]* v1, double[<3>]* v2) => ortho_normalize3(v1, v2); - -// -- private helpers - -macro towards(v, target, max_distance) @private -{ - var delta = target - v; - var square = delta.sq_magnitude(); - - if (square == 0 || (max_distance >= 0 && (square <= max_distance * max_distance))) return target; - - var dist = math::sqrt(square); - - return v + delta * max_distance / dist; -} - -macro clamp_magnitude(v, min, max) @private -{ - var length = v.sq_magnitude(); - if (length > 0) - { - length = math::sqrt(length); - - if (length < min) return v * (min / length); - if (length > max) return v * (max / length); - } - return v; -} - -macro rotate(v, angle) @private -{ - var c = math::cos(angle); - var s = math::sin(angle); - return ($typeof(v)) { v[0] * c - v[1] * s, v[0] * s + v[1] * c }; -} - -macro perpendicular3(v) @private -{ - var min = math::abs(v[0]); - $typeof(v) cardinal_axis = { 1, 0, 0 }; - - if (var vy = math::abs(v[1]), vy < min) - { - min = vy; - cardinal_axis = { 0, 1, 0 }; - } - - if (var vz = math::abs(v[2]), vz < min) - { - cardinal_axis = { 0, 0, 1 }; - } - - return cross3(v, cardinal_axis); -} - -macro cross3(v1, v2) @private -{ - var a = v1.yzx * v2.zxy; - var b = v1.zxy * v2.yzx; - return a - b; -} - -macro transform2(v, mat) @private -{ - return ($typeof(v)) { - mat.m00 * v[0] + mat.m10 * v[1] + mat.m30 , - mat.m01 * v[0] + mat.m11 * v[1] + mat.m31 }; -} - -macro transform3(v, mat) @private -{ - return ($typeof(v)){ - mat.m00 * v[0] + mat.m10 * v[1] + mat.m20 * v[2] + mat.m30, - mat.m01 * v[0] + mat.m11 * v[1] + mat.m21 * v[2] + mat.m31, - mat.m02 * v[0] + mat.m12 * v[1] + mat.m22 * v[2] + mat.m32 - }; -} - - -macro angle3(v1, v2) @private -{ - var len = v1.cross(v2).length(); - var dot = v1.dot(v2); - return math::atan2(len, dot); -} - -macro void ortho_normalize3(v1, v2) @private -{ - var v1n = *v1 = v1.normalize(); - var vn1 = v1n.cross(*v2).normalize(); - *v2 = v1n.cross(vn1); -} - -macro rotate_axis_angle(v, axis, angle) @private -{ - axis = axis.normalize(); - - angle /= 2; - var w = axis * math::sin(angle); - var wv = w.cross(v); - var wwv = w.cross(wv); - wv *= math::cos(($typeof(v))angle) * 2; - wwv *= 2; - - return v + wv + wwv; -} - -macro unproject3(v, m1, m2) @private -{ -return v; -/* - var view_proj = m1.mul(m2); - var invert = view_proj.invert(); - // Create quaternion from source point - $if @typeid(v[0]) == float.typeid: - Quaternionf quat = { v.x, v.y, v.z, 1 }; - $else - Quaternion quat = { v.x, v.y, v.z, 1 }; - $endif - - // Multiply quat point by unproject matrix - var qtransformed = quat.transform(invert); - - // Normalized world points in vectors - return { - qtransformed.i / qtransformed.l, - qtransformed.j / qtransformed.l, - qtransformed.k / qtransformed.l - };*/ -} - -macro barycenter3(p, a, b, c) @private -{ - var v0 = b - a; - var v1 = c - a; - var v2 = p - a; - var d00 = v0.dot(v0); - var d01 = v0.dot(v1); - var d11 = v1.dot(v1); - var d20 = v2.dot(v0); - var d21 = v2.dot(v1); - var denom = d00 * d11 - d01 * d01; - var y = (d11 * d20 - d01 * d21) / denom; - var z = (d00 * d21 - d01 * d20) / denom; - return ($typeof(p)){ 1 - y - z, y, z }; -} - -macro refract3(v, n, r) @private -{ - var dot = v.dot(n); - var d = 1 - r * r * (1 - dot * dot); - - return d < 0 ? v : r * v - (r * dot + math::sqrt(d)) * n; -} diff --git a/test/stdlib/src/math/vector_private.c3 b/test/stdlib/src/math/vector_private.c3 new file mode 100644 index 0000000..dd374aa --- /dev/null +++ b/test/stdlib/src/math/vector_private.c3 @@ -0,0 +1,205 @@ +module std::math::vector @private; +import std::math; + +macro cubic_hermite3(v1, tangent1, v2, tangent2, amount) +{ + var amount_pow2 = amount * amount; + var amount_pow3 = amount_pow2 * amount; + + return (2 * amount_pow3 - 3 *amount_pow2 + 1) * v1 + + (amount_pow3 - 2 * amount_pow2 + amount) * tangent1 + + (-2 * amount_pow3 + 3 * amount_pow2) * v2 + + (amount_pow3 - amount_pow2) * tangent2; +} + +fn TowardsType towards(TowardsType v, TowardsType target, $typefrom(TowardsType::inner) max_distance) +{ + TowardsType delta = target - v; + var square @safeinfer = delta.length_sq(); + + if (square == 0 || (max_distance >= 0 && (square <= max_distance * max_distance))) return target; + + return v + delta * max_distance / math::sqrt(square); +} + +fn ClampType clamp_length(ClampType v, $typefrom(ClampType::inner) min, $typefrom(ClampType::inner) max) +{ + var length @safeinfer = v.length_sq(); + if (length > 0) + { + length = math::sqrt(length); + + if (length < min) return v * (min / length); + if (length > max) return v * (max / length); + } + return v; +} + +macro rotate(v, angle) +{ + var c = math::cos(angle); + var s = math::sin(angle); + return ($typeof(v)) { v[0] * c - v[1] * s, v[0] * s + v[1] * c }; +} + +macro perpendicular3(v) +{ + var min = math::abs(v[0]); + $typeof(v) cardinal_axis = { 1, 0, 0 }; + + if (var vy = math::abs(v[1]), vy < min) + { + min = vy; + cardinal_axis = { 0, 1, 0 }; + } + + if (var vz = math::abs(v[2]), vz < min) + { + cardinal_axis = { 0, 0, 1 }; + } + + return cross3(v, cardinal_axis); +} + +macro cross2(v1, v2) +{ + return v1.x * v2.y - v1.y * v2.x; +} + +macro cross3(v1, v2) +{ + var a = v1.yzx * v2.zxy; + var b = v1.zxy * v2.yzx; + return a - b; +} + +macro transform2(v, mat) +{ + return ($typeof(v)) { + mat.m00 * v[0] + mat.m10 * v[1] + mat.m30 , + mat.m01 * v[0] + mat.m11 * v[1] + mat.m31 }; +} + +macro transform3(v, mat) +{ + return ($typeof(v)){ + mat.m00 * v[0] + mat.m10 * v[1] + mat.m20 * v[2] + mat.m30, + mat.m01 * v[0] + mat.m11 * v[1] + mat.m21 * v[2] + mat.m31, + mat.m02 * v[0] + mat.m12 * v[1] + mat.m22 * v[2] + mat.m32 + }; +} + +macro angle3(v1, v2) +{ + var len = v1.cross(v2).length(); + var dot = v1.dot(v2); + return math::atan2(len, dot); +} + +macro void ortho_normalize3(v1, v2) +{ + var v1n = *v1 = v1.normalize(); + var vn1 = v1n.cross(*v2).normalize(); + *v2 = v1n.cross(vn1); +} + + +macro rotate_axis_angle(v, axis, angle) +{ + axis = axis.normalize(); + + angle /= 2; + var w = axis * math::sin(angle); + var wv = w.cross(v); + var wwv = w.cross(wv); + wv *= math::cos(($typeof(v))angle) * 2; + wwv *= 2; + + return v + wv + wwv; +} + +macro rejection3(v1, v2) +{ + return v1 - v2 * (v1.dot(v2) / v2.dot(v2)); +} + +macro project3(v, model, proj, viewport) +{ + var $Element = $typeof(v)::inner; + $Element[<4>] tmp = { ...v, 1 }; + tmp = model * tmp; + tmp = proj * tmp; + tmp /= tmp.w; + tmp = { (tmp.x * 0.5 + 0.5) * viewport.z + viewport.x, (tmp.y * 0.5 + 0.5) * viewport.w + viewport.y, tmp.z, tmp.w }; + return tmp.xyz; +} + +macro unproject3(v, model, proj, viewport) +{ + var mat_view_inverse = (proj * model).inverse()!!; + + var $Element = $typeof(v)::inner; + + $Element[<4>] tmp = { + 2 * (v.x - viewport.x) / viewport.z - 1, + 2 * (v.y - viewport.y) / viewport.w - 1, + v.z, + 1 }; + tmp = mat_view_inverse * tmp; + return tmp.xyz / tmp.w; +} + +macro barycentric3(p, a, b, c) +{ + var v0 = b - a; + var v1 = c - a; + var v2 = p - a; + var d00 = v0.dot(v0); + var d01 = v0.dot(v1); + var d11 = v1.dot(v1); + var d20 = v2.dot(v0); + var d21 = v2.dot(v1); + var denom = d00 * d11 - d01 * d01; + var y = (d11 * d20 - d01 * d21) / denom; + var z = (d00 * d21 - d01 * d20) / denom; + return ($typeof(p)){ 1 - y - z, y, z }; +} + +macro refract3(v, n, r) +{ + var dot = v.dot(n); + var d = 1 - r * r * (1 - dot * dot); + + return d < 0 ? v : r * v - (r * dot + math::sqrt(d)) * n; +} + + +macro equals(v1, v2) +{ + var epsilon = ($typeof(v1)::inner.kind == FLOAT ??? math::FLOAT_VECTOR_EPSILON : math::DOUBLE_VECTOR_EPSILON) * math::max(math::abs(v1), math::abs(v2), ($typeof(v1))1); + return math::abs(v1 - v2).comp_le(epsilon).and(); +} + +macro not_equals(v1, v2) +{ + var epsilon = ($typeof(v1)::inner.kind == FLOAT ??? math::FLOAT_VECTOR_EPSILON : math::DOUBLE_VECTOR_EPSILON) * math::max(math::abs(v1), math::abs(v2), ($typeof(v1))1); + + return math::abs(v1 - v2).comp_gt(epsilon).or(); +} + +macro bool @is_same_vector_or_scalar(#vector_value, #vector_or_scalar) +{ + return (values::@is_vector(#vector_or_scalar) &&& values::@is_same_vector_type(#vector_value, #vector_or_scalar)) ||| values::@is_int(#vector_or_scalar); +} + +macro reflect(x, y) +{ + return x - 2 * y * x.dot(y); +} + +macro normalize(x) +{ + var len = x.length(); + if (len == 0) return x; + return x * (1 / len); +} diff --git a/test/stdlib/src/net/inetaddr.c3 b/test/stdlib/src/net/inetaddr.c3 index 41c2936..fef82fc 100644 --- a/test/stdlib/src/net/inetaddr.c3 +++ b/test/stdlib/src/net/inetaddr.c3 @@ -44,7 +44,7 @@ struct InetAddress (Printable) } -fn usz? InetAddress.to_format(InetAddress* addr, Formatter* formatter) @dynamic +fn sz? InetAddress.to_format(InetAddress* addr, Formatter* formatter) @dynamic { if (addr.is_ipv6) { @@ -67,13 +67,13 @@ fn String InetAddress.to_tstring(&self) fn InetAddress? ipv6_from_str(String s) { - uint sections = 0; + int sections = 0; if (s.len < 2) return INVALID_IP_STRING~; foreach (c : s) if (c == ':') sections++; int zero_segment_len = s[0] == ':' || s[^1] == ':' ? 9 - sections : 8 - sections; if (zero_segment_len == 7 && s.len == 2) return { .is_ipv6 = true }; if (zero_segment_len > 7) return INVALID_IP_STRING~; - usz index = 0; + sz index = 0; bool last_was_colon, found_zero; int current = -1; InetAddress addr = { .is_ipv6 = true }; @@ -108,7 +108,7 @@ fn InetAddress? ipv6_from_str(String s) last_was_colon = false; if (index > 7 || !c.is_xdigit()) return INVALID_IP_STRING~; if (current < 0) current = 0; - current = current * 16 + (c <= '9' ? c - '0' : (c | 32) - 'a' + 10); + current = current * 16 + (c <= '9' ? (int)(c - '0') : (c | 32) - 'a' + 10); } // Ends with :: if (index == 8 && current == -1) return addr; @@ -144,7 +144,7 @@ fn InetAddress? ipv4_from_str(String s) if (element > 3 || c < '0' || c > '9') return INVALID_IP_STRING~; if (current < 0) { - current = c - '0'; + current = (int)c - '0'; continue; } current = current * 10 + c - '0'; @@ -251,8 +251,15 @@ fn bool InetAddress.is_multicast_link_local(InetAddress* addr) return addr.ipv4.a == 224 && addr.ipv4.b == 0 && addr.ipv4.c == 0; } -fn AddrInfo*? addrinfo(String host, uint port, AIFamily ai_family, AISockType ai_socktype) @if(os::SUPPORTS_INET) => @pool() +<* + @require port >= 0 && port <= 0xFFFF : "Invalid port" +*> +fn AddrInfo*? addrinfo(String host, int port, AIFamily ai_family, AISockType ai_socktype) @if(os::SUPPORTS_INET) => @pool() { + $if env::WIN32: //fn TcpServerSocket? listen() calls addrinfo() before setting os::start_wsa so we need this here + os::start_wsa()!; + $endif + ZString zhost = host.zstr_tcopy(); DString str = dstring::temp_with_capacity(32); str.appendf("%d", port); diff --git a/test/stdlib/src/net/net.c3 b/test/stdlib/src/net/net.c3 index c427b79..14f6735 100644 --- a/test/stdlib/src/net/net.c3 +++ b/test/stdlib/src/net/net.c3 @@ -38,7 +38,7 @@ fn uint? ipv4toint(String s) if (c == '.') { if (current < 0) return INVALID_IP_STRING~; - out = out << 8 + current; + out = out << 8u + (uint)current; current = -1; element++; continue; @@ -46,13 +46,13 @@ fn uint? ipv4toint(String s) if (element > 3 || c < '0' || c > '9') return INVALID_IP_STRING~; if (current < 0) { - current = c - '0'; + current = (int)c - '0'; continue; } current = current * 10 + c - '0'; } if (element != 3 || current < 0) return INVALID_IP_STRING~; - out = out << 8 + current; + out = out << 8u + (uint)current; return out; } diff --git a/test/stdlib/src/net/os/android.c3 b/test/stdlib/src/net/os/android.c3 index 10fecb6..9d90aff 100644 --- a/test/stdlib/src/net/os/android.c3 +++ b/test/stdlib/src/net/os/android.c3 @@ -1,14 +1,14 @@ module std::net::os @if(env::ANDROID); import libc; -const AIFamily PLATFORM_AF_AX25 = 3; -const AIFamily PLATFORM_AF_IPX = 4; -const AIFamily PLATFORM_AF_APPLETALK = 5; -const AIFamily PLATFORM_AF_NETROM = 6; -const AIFamily PLATFORM_AF_BRIDGE = 7; -const AIFamily PLATFORM_AF_AAL5 = 8; -const AIFamily PLATFORM_AF_X25 = 9; -const AIFamily PLATFORM_AF_INET6 = 10; +const AIFamily PLATFORM_AF_AX25 = (AIFamily)3; +const AIFamily PLATFORM_AF_IPX = (AIFamily)4; +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)5; +const AIFamily PLATFORM_AF_NETROM = (AIFamily)6; +const AIFamily PLATFORM_AF_BRIDGE = (AIFamily)7; +const AIFamily PLATFORM_AF_AAL5 = (AIFamily)8; +const AIFamily PLATFORM_AF_X25 = (AIFamily)9; +const AIFamily PLATFORM_AF_INET6 = (AIFamily)10; const PLATFORM_O_NONBLOCK = 0o4000; diff --git a/test/stdlib/src/net/os/common.c3 b/test/stdlib/src/net/os/common.c3 index 27d32d0..4de82a7 100644 --- a/test/stdlib/src/net/os/common.c3 +++ b/test/stdlib/src/net/os/common.c3 @@ -1,5 +1,5 @@ module std::net::os; -const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD || env::NETBSD); +const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD || env::NETBSD || env::FREEBSD); typedef AIFamily = CInt; typedef AIProtocol = CInt; @@ -23,7 +23,7 @@ struct AddrInfo ZString ai_canonname; SockAddrPtr ai_addr; } - struct @if(env::LINUX || env::OPENBSD) + struct @if(env::LINUX || env::OPENBSD || env::FREEBSD) { SockAddrPtr ai_addr; ZString ai_canonname; @@ -87,11 +87,11 @@ extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* o *> extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t* optlen) @if(SUPPORTS_INET); -module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD || env::NETBSD)); +module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD || env::NETBSD || env::FREEBSD)); -const AIFamily PLATFORM_AF_INET6 = 0; -const AIFamily PLATFORM_AF_IPX = 0; -const AIFamily PLATFORM_AF_APPLETALK = 0; +const AIFamily PLATFORM_AF_INET6 = (AIFamily)0; +const AIFamily PLATFORM_AF_IPX = (AIFamily)0; +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)0; const IPPPOTO_IP = 0; const IPPROTO_ICMP = 1; diff --git a/test/stdlib/src/net/os/freebsd.c3 b/test/stdlib/src/net/os/freebsd.c3 new file mode 100644 index 0000000..b6c18e3 --- /dev/null +++ b/test/stdlib/src/net/os/freebsd.c3 @@ -0,0 +1,206 @@ +module std::net::os @if(env::FREEBSD); +import libc; + +// all of this, see FreeBSD /usr/include/sys/socket.h + + +// Creation flags, OR'ed into socket() and socketpair() type argument. +const SOCK_CLOEXEC = 0x10000000; +const SOCK_NONBLOCK = 0x20000000; +const SOCK_CLOFORK = 0x40000000; + + +// Option flags per-socket. + +const int SO_DEBUG = 0x00000001; // turn on debugging info recording +const int SO_ACCEPTCONN = 0x00000002; // socket has had listen() +const int SO_REUSEADDR = 0x00000004; // allow local address reuse +const int SO_KEEPALIVE = 0x00000008; // keep connections alive +const int SO_DONTROUTE = 0x00000010; // just use interface addresses +const int SO_BROADCAST = 0x00000020; // permit sending of broadcast msgs +const int SO_USELOOPBACK = 0x00000040; // bypass hardware when possible +const int SO_LINGER = 0x00000080; // linger on close if data present +const int SO_OOBINLINE = 0x00000100; // leave received OOB data in line +const int SO_REUSEPORT = 0x00000200; // allow local address & port reuse +const int SO_TIMESTAMP = 0x00000400; // timestamp received dgram traffic +const int SO_NOSIGPIPE = 0x00000800; // no SIGPIPE from EPIPE +const int SO_ACCEPTFILTER = 0x00001000; // there is an accept filter +const int SO_BINTIME = 0x00002000; // timestamp received dgram traffic +const int SO_NO_OFFLOAD = 0x00004000; // socket cannot be offloaded +const int SO_NO_DDP = 0x00008000; // disable direct data placement +const int SO_REUSEPORT_LB = 0x00010000; // reuse with load balancing +const int SO_RERROR = 0x00020000; // keep track of receive errors + + + +// Additional options, not kept in so_options. + +const int SO_SNDBUF = 0x1001; // send buffer size +const int SO_RCVBUF = 0x1002; // receive buffer size +const int SO_SNDLOWAT = 0x1003; // send low-water mark +const int SO_RCVLOWAT = 0x1004; // receive low-water mark +const int SO_SNDTIMEO = 0x1005; // send timeout +const int SO_RCVTIMEO = 0x1006; // receive timeout +const int SO_ERROR = 0x1007; // get error status and clear +const int SO_TYPE = 0x1008; // get socket type +const int SO_LABEL = 0x1009; // socket's MAC label +const int SO_PEERLABEL = 0x1010; // socket's peer's MAC label +const int SO_LISTENQLIMIT = 0x1011; // socket's backlog limit +const int SO_LISTENQLEN = 0x1012; // socket's complete queue lenth +const int SO_LISTENINCQLEN = 0x1013; // socket's incomplete queue lenth +const int SO_FIB = 0x1014; // get or set socket FIB +const int SO_SETFIB = SO_FIB; // backward compat alias +const int SO_USER_COOKIE = 0x1015; // user cookie (dummynet etc.) +const int SO_PROTOCOL = 0x1016; // get socket protocol (Linux name) +const int SO_PROTOTYPE = SO_PROTOCOL; // alias for SO_PROTOCOL (SunOS name) +const int SO_TS_CLOCK = 0x1017; // clock type used for SO_TIMESTAMP +const int SO_MAX_PACING_RATE = 0x1018; // socket's max TX pacing rate (Linux name) +const int SO_DOMAIN = 0x1019; // get socket domain +const int SO_SPLICE = 0x1023; // splice data to other socket + +const SO_TS_REALTIME_MICRO = 0; // microsecond resolution, realtime +const SO_TS_BINTIME = 1; // sub-nanosecond resolution, realtime +const SO_TS_REALTIME = 2; // nanosecond resolution, realtime +const SO_TS_MONOTONIC = 3; // nanosecond resolution, monotonic +const SO_TS_DEFAULT = SO_TS_REALTIME_MICRO; +const SO_TS_CLOCK_MAX = SO_TS_MONOTONIC; + +const int SOL_SOCKET = 0xFFFF; // options for socket level + + +// Address families. + +const AIFamily PLATFORM_AF_UNSPEC = (AIFamily)0; // unspecified +const AIFamily PLATFORM_AF_UNIX = (AIFamily)1; // standardized name for AF_LOCAL +const AIFamily PLATFORM_AF_LOCAL = (AIFamily)PLATFORM_AF_UNIX; // local to host (pipes, portals) +const AIFamily PLATFORM_AF_INET = (AIFamily)2; // internetwork: UDP, TCP, etc. +const AIFamily PLATFORM_AF_IMPLINK = (AIFamily)3; // arpanet imp addresses +const AIFamily PLATFORM_AF_PUP = (AIFamily)4; // pup protocols: e.g. BSP +const AIFamily PLATFORM_AF_CHAOS = (AIFamily)5; // mit CHAOS protocols +const AIFamily PLATFORM_AF_NETBIOS = (AIFamily)6; // SMB protocols +const AIFamily PLATFORM_AF_ISO = (AIFamily)7; // ISO protocols +const AIFamily PLATFORM_AF_OSI = (AIFamily)PLATFORM_AF_ISO; +const AIFamily PLATFORM_AF_ECMA = (AIFamily)8; // European computer manufacturers +const AIFamily PLATFORM_AF_DATAKIT = (AIFamily)9; // datakit protocols +const AIFamily PLATFORM_AF_CCITT = (AIFamily)10; // CCITT protocols, X.25 etc +const AIFamily PLATFORM_AF_SNA = (AIFamily)11; // IBM SNA +const AIFamily PLATFORM_AF_DECNET = (AIFamily)12; // DECnet +const AIFamily PLATFORM_AF_DLI = (AIFamily)13; // DEC Direct data link interface +const AIFamily PLATFORM_AF_LAT = (AIFamily)14; // LAT +const AIFamily PLATFORM_AF_HYLINK = (AIFamily)15; // NSC Hyperchannel +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)16; // Apple Talk +const AIFamily PLATFORM_AF_ROUTE = (AIFamily)17; // Internal Routing Protocol +const AIFamily PLATFORM_AF_LINK = (AIFamily)18; // Link layer interface +const AIFamily PLATFORM_PSEUDO_AF_XTP = (AIFamily)19; // eXpress Transfer Protocol (no AF) +const AIFamily PLATFORM_AF_COIP = (AIFamily)20; // connection-oriented IP, aka ST II +const AIFamily PLATFORM_AF_CNT = (AIFamily)21; // Computer Network Technology +const AIFamily PLATFORM_PSEUDO_AF_RTIP = (AIFamily)22; // Help Identify RTIP packets +const AIFamily PLATFORM_AF_IPX = (AIFamily)23; // Novell Internet Protocol +const AIFamily PLATFORM_AF_SIP = (AIFamily)24; // Simple Internet Protocol +const AIFamily PLATFORM_PSEUDO_AF_PIP = (AIFamily)25; // Help Identify PIP packets +const AIFamily PLATFORM_AF_ISDN = (AIFamily)26; // Integrated Services Digital Network +const AIFamily PLATFORM_AF_E164 = (AIFamily)PLATFORM_AF_ISDN; // CCITT E.164 recommendation +const AIFamily PLATFORM_PSEUDO_AF_KEY = (AIFamily)27; // Internal key-management function +const AIFamily PLATFORM_AF_INET6 = (AIFamily)28; // IPv6 +const AIFamily PLATFORM_AF_NATM = (AIFamily)29; // native ATM access +const AIFamily PLATFORM_AF_ATM = (AIFamily)30; // ATM +const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = (AIFamily)31; // Used by BPF to not rewrite headers in interface output routine +const AIFamily PLATFORM_AF_NETGRAPH = (AIFamily)32; // Netgraph sockets +const AIFamily PLATFORM_AF_SLOW = (AIFamily)33; // 802.3ad slow protocol +const AIFamily PLATFORM_AF_SCLUSTER = (AIFamily)34; // Sitara cluster protocol +const AIFamily PLATFORM_AF_ARP = (AIFamily)35; +const AIFamily PLATFORM_AF_BLUETOOTH = (AIFamily)36; // Bluetooth sockets +const AIFamily PLATFORM_AF_IEEE80211 = (AIFamily)37; // IEEE 802.11 protocol +const AIFamily PLATFORM_AF_NETLINK = (AIFamily)38; // Netlink protocol +const AIFamily PLATFORM_AF_INET_SDP = (AIFamily)40; // OFED Socket Direct Protocol ipv4 +const AIFamily PLATFORM_AF_INET6_SDP = (AIFamily)42; // OFED Socket Direct Protocol ipv6 +const AIFamily PLATFORM_AF_HYPERV = (AIFamily)43; // HyperV sockets +const AIFamily PLATFORM_AF_DIVERT = (AIFamily)44; // divert(4) +const AIFamily PLATFORM_AF_IPFWLOG = (AIFamily)46; +const AIFamily PLATFORM_AF_MAX = (AIFamily)46; + +const int SOCK_MAXADDRLEN = 255; // longest possible addresses + + +//Protocol families, same as address families for now. + +const PF_UNSPEC = PLATFORM_AF_UNSPEC; +const PF_LOCAL = PLATFORM_AF_LOCAL; +const PF_UNIX = PLATFORM_AF_LOCAL; // backward compatibility +const PF_INET = PLATFORM_AF_INET; +const PF_IMPLINK = PLATFORM_AF_IMPLINK; +const PF_PUP = PLATFORM_AF_PUP; +const PF_CHAOS = PLATFORM_AF_CHAOS; +const PF_NETBIOS = PLATFORM_AF_NETBIOS; +const PF_ISO = PLATFORM_AF_ISO; +const PF_OSI = PLATFORM_AF_ISO; +const PF_ECMA = PLATFORM_AF_ECMA; +const PF_DATAKIT = PLATFORM_AF_DATAKIT; +const PF_CCITT = PLATFORM_AF_CCITT; +const PF_SNA = PLATFORM_AF_SNA; +const PF_DECNET = PLATFORM_AF_DECNET; +const PF_DLI = PLATFORM_AF_DLI; +const PF_LAT = PLATFORM_AF_LAT; +const PF_HYLINK = PLATFORM_AF_HYLINK; +const PF_APPLETALK = PLATFORM_AF_APPLETALK; +const PF_ROUTE = PLATFORM_AF_ROUTE; +const PF_LINK = PLATFORM_AF_LINK; +const PF_XTP = PLATFORM_PSEUDO_AF_XTP; // really just proto family, no AF +const PF_COIP = PLATFORM_AF_COIP; +const PF_CNT = PLATFORM_AF_CNT; +const PF_SIP = PLATFORM_AF_SIP; +const PF_IPX = PLATFORM_AF_IPX; +const PF_RTIP = PLATFORM_PSEUDO_AF_RTIP; // same format as AF_INET +const PF_PIP = PLATFORM_PSEUDO_AF_PIP; +const PF_ISDN = PLATFORM_AF_ISDN; +const PF_KEY = PLATFORM_PSEUDO_AF_KEY; +const PF_INET6 = PLATFORM_AF_INET6; +const PF_NATM = PLATFORM_AF_NATM; +const PF_ATM = PLATFORM_AF_ATM; +const PF_NETGRAPH = PLATFORM_AF_NETGRAPH; +const PF_SLOW = PLATFORM_AF_SLOW; +const PF_SCLUSTER = PLATFORM_AF_SCLUSTER; +const PF_ARP = PLATFORM_AF_ARP; +const PF_BLUETOOTH = PLATFORM_AF_BLUETOOTH; +const PF_IEEE80211 = PLATFORM_AF_IEEE80211; +const PF_NETLINK = PLATFORM_AF_NETLINK; +const PF_INET_SDP = PLATFORM_AF_INET_SDP; +const PF_INET6_SDP = PLATFORM_AF_INET6_SDP; +const PF_DIVERT = PLATFORM_AF_DIVERT; +const PF_IPFWLOG = PLATFORM_AF_IPFWLOG; +const PF_MAX = PLATFORM_AF_MAX; + +// TODO: pickup from socket.h at #; also look at pool.h for what need to be implemented + +//Maximum queue length specifiable by listen. +const int SOMAXCONN = 128; + +// POLLIN through POLLNVAL are predefined by lib/std/net/os/posix.c3 +const CUShort POLLRDNORM = 0x0040; // non-OOB/URG data available +const CUShort POLLWRNORM = POLLOUT; // no write type differentiation +const CUShort POLLRDBAND = 0x0080; // OOB/Urgent readable data +const CUShort POLLWRBAND = 0x0100; // OOB/Urgent data can be written +const CUShort POLLINIGNEOF = 0x2000; // like POLLIN, except ignore EOF +const CUShort POLLRDHUP = 0x4000; // half shut down + + + +const CInt MSG_OOB = 0x00000001; // process out-of-band data +const CInt MSG_PEEK = 0x00000002; // peek at incoming message +const CInt MSG_DONTROUTE = 0x00000004; // send without using routing tables +const CInt MSG_EOR = 0x00000008; // data completes record +const CInt MSG_TRUNC = 0x00000010; // data discarded before delivery +const CInt MSG_CTRUNC = 0x00000020; // control data lost before delivery +const CInt MSG_WAITALL = 0x00000040; // wait for full request or error +const CInt MSG_DONTWAIT = 0x00000080; // this message should be nonblocking +const CInt MSG_EOF = 0x00000100; // data completes connection +const CInt MSG_NOTIFICATION = 0x00002000; // SCTP notification +const CInt MSG_NBIO = 0x00004000; // FIONBIO mode, used by fifofs +const CInt MSG_COMPAT = 0x00008000; // used in sendit() +const CInt MSG_NOSIGNAL = 0x00020000; // do not generate SIGPIPE on EOF +const CInt MSG_CMSG_CLOEXEC = 0x00040000; // make received fds close-on-exec +const CInt MSG_WAITFORONE = 0x00080000; // for recvmmsg() +const CInt MSG_CMSG_CLOFORK = 0x00400000; // make received fds close-on-fork + + +const PLATFORM_O_NONBLOCK = SOCK_NONBLOCK; diff --git a/test/stdlib/src/net/os/linux.c3 b/test/stdlib/src/net/os/linux.c3 index fb800ff..a880641 100644 --- a/test/stdlib/src/net/os/linux.c3 +++ b/test/stdlib/src/net/os/linux.c3 @@ -1,14 +1,14 @@ module std::net::os @if(env::LINUX); import libc; -const AIFamily PLATFORM_AF_AX25 = 3; -const AIFamily PLATFORM_AF_IPX = 4; -const AIFamily PLATFORM_AF_APPLETALK = 5; -const AIFamily PLATFORM_AF_NETROM = 6; -const AIFamily PLATFORM_AF_BRIDGE = 7; -const AIFamily PLATFORM_AF_AAL5 = 8; -const AIFamily PLATFORM_AF_X25 = 9; -const AIFamily PLATFORM_AF_INET6 = 10; +const AIFamily PLATFORM_AF_AX25 = (AIFamily)3; +const AIFamily PLATFORM_AF_IPX = (AIFamily)4; +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)5; +const AIFamily PLATFORM_AF_NETROM = (AIFamily)6; +const AIFamily PLATFORM_AF_BRIDGE = (AIFamily)7; +const AIFamily PLATFORM_AF_AAL5 = (AIFamily)8; +const AIFamily PLATFORM_AF_X25 = (AIFamily)9; +const AIFamily PLATFORM_AF_INET6 = (AIFamily)10; const PLATFORM_O_NONBLOCK = 0o4000; diff --git a/test/stdlib/src/net/os/netbsd.c3 b/test/stdlib/src/net/os/netbsd.c3 index 3932a0c..a0b1179 100644 --- a/test/stdlib/src/net/os/netbsd.c3 +++ b/test/stdlib/src/net/os/netbsd.c3 @@ -1,48 +1,47 @@ module std::net::os @if(env::NETBSD); import libc; -const AIFamily PLATFORM_AF_UNSPEC = 0; // unspecified -const AIFamily PLATFORM_AF_LOCAL = 1; // local to host -const AIFamily PLATFORM_AF_UNIX = PLATFORM_AF_LOCAL; // backward compatibility -const AIFamily PLATFORM_AF_INET = 2; // internetwork: UDP, TCP, etc. -const AIFamily PLATFORM_AF_IMPLINK = 3; // arpanet imp addresses -const AIFamily PLATFORM_AF_PUP = 4; // pup protocols: e.g. BSP -const AIFamily PLATFORM_AF_CHAOS = 5; // mit CHAOS protocols -const AIFamily PLATFORM_AF_NS = 6; // XEROX NS protocols -const AIFamily PLATFORM_AF_ISO = 7; // ISO protocols -const AIFamily PLATFORM_AF_OSI = PLATFORM_AF_ISO; -const AIFamily PLATFORM_AF_ECMA = 8; // european computer manufacturers -const AIFamily PLATFORM_AF_DATAKIT = 9; // datakit protocols -const AIFamily PLATFORM_AF_CCITT = 10; // CCITT protocols, X.25 etc -const AIFamily PLATFORM_AF_SNA = 11; // IBM SNA -const AIFamily PLATFORM_AF_DECNET = 12; // DECnet -const AIFamily PLATFORM_AF_DLI = 13; // DEC Direct data link interface -const AIFamily PLATFORM_AF_LAT = 14; // LAT -const AIFamily PLATFORM_AF_HYLINK = 15; // NSC Hyperchannel -const AIFamily PLATFORM_AF_APPLETALK = 16; // Apple Talk -const AIFamily PLATFORM_AF_OROUTE = 17; // Internal Routing Protocol -const AIFamily PLATFORM_AF_LINK = 18; // Link layer interface -const AIFamily PLATFORM_PSEUDO_AF_XTP = 19; // eXpress Transfer Protocol (no AF) -const AIFamily PLATFORM_AF_COIP = 20; // connection-oriented IP, aka ST II -const AIFamily PLATFORM_AF_CNT = 21; // Computer Network Technology -const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22; // Help Identify RTIP packets -const AIFamily PLATFORM_AF_IPX = 23; // Novell Internet Protocol -const AIFamily PLATFORM_AF_INET6 = 24; // IP version 6 -const AIFamily PLATFORM_PSEUDO_AF_PIP = 25; // Help Identify PIP packets -const AIFamily PLATFORM_AF_ISDN = 26; // Integrated Services Digital Networ -const AIFamily PLATFORM_AF_E164 = PLATFORM_AF_ISDN; // CCITT E.164 recommendation -const AIFamily PLATFORM_AF_NATM = 27; // native ATM access -const AIFamily PLATFORM_AF_ARP = 28; // (rev.) addr. res. prot. (RFC 826) -const AIFamily PLATFORM_PSEUDO_AF_KEY = 29; // Internal key management protocol -const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 30; /* Used by BPF to not rewrite hdrs - * in interface output routine */ -const AIFamily PLATFORM_AF_BLUETOOTH = 31; // Bluetooth: HCI, SCO, L2CAP, RFCOMM -const AIFamily PLATFORM_AF_IEEE80211 = 32; // IEEE80211 -const AIFamily PLATFORM_AF_MPLS = 33; // MultiProtocol Label Switching -const AIFamily PLATFORM_AF_ROUTE = 34; // Internal Routing Protocol -const AIFamily PLATFORM_AF_CAN = 35; -const AIFamily PLATFORM_AF_ETHER = 36; -const AIFamily PLATFORM_AF_MAX = 37; +const AIFamily PLATFORM_AF_UNSPEC = (AIFamily)0; // unspecified +const AIFamily PLATFORM_AF_LOCAL = (AIFamily)1; // local to host +const AIFamily PLATFORM_AF_UNIX = (AIFamily)PLATFORM_AF_LOCAL; // backward compatibility +const AIFamily PLATFORM_AF_INET = (AIFamily)2; // internetwork: UDP, TCP, etc. +const AIFamily PLATFORM_AF_IMPLINK = (AIFamily)3; // arpanet imp addresses +const AIFamily PLATFORM_AF_PUP = (AIFamily)4; // pup protocols: e.g. BSP +const AIFamily PLATFORM_AF_CHAOS = (AIFamily)5; // mit CHAOS protocols +const AIFamily PLATFORM_AF_NS = (AIFamily)6; // XEROX NS protocols +const AIFamily PLATFORM_AF_ISO = (AIFamily)7; // ISO protocols +const AIFamily PLATFORM_AF_OSI = (AIFamily)PLATFORM_AF_ISO; +const AIFamily PLATFORM_AF_ECMA = (AIFamily)8; // european computer manufacturers +const AIFamily PLATFORM_AF_DATAKIT = (AIFamily)9; // datakit protocols +const AIFamily PLATFORM_AF_CCITT = (AIFamily)10; // CCITT protocols, X.25 etc +const AIFamily PLATFORM_AF_SNA = (AIFamily)11; // IBM SNA +const AIFamily PLATFORM_AF_DECNET = (AIFamily)12; // DECnet +const AIFamily PLATFORM_AF_DLI = (AIFamily)13; // DEC Direct data link interface +const AIFamily PLATFORM_AF_LAT = (AIFamily)14; // LAT +const AIFamily PLATFORM_AF_HYLINK = (AIFamily)15; // NSC Hyperchannel +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)16; // Apple Talk +const AIFamily PLATFORM_AF_OROUTE = (AIFamily)17; // Internal Routing Protocol +const AIFamily PLATFORM_AF_LINK = (AIFamily)18; // Link layer interface +const AIFamily PLATFORM_PSEUDO_AF_XTP = (AIFamily)19; // eXpress Transfer Protocol (no AF) +const AIFamily PLATFORM_AF_COIP = (AIFamily)20; // connection-oriented IP, aka ST II +const AIFamily PLATFORM_AF_CNT = (AIFamily)21; // Computer Network Technology +const AIFamily PLATFORM_PSEUDO_AF_RTIP = (AIFamily)22; // Help Identify RTIP packets +const AIFamily PLATFORM_AF_IPX = (AIFamily)23; // Novell Internet Protocol +const AIFamily PLATFORM_AF_INET6 = (AIFamily)24; // IP version 6 +const AIFamily PLATFORM_PSEUDO_AF_PIP = (AIFamily)25; // Help Identify PIP packets +const AIFamily PLATFORM_AF_ISDN = (AIFamily)26; // Integrated Services Digital Networ +const AIFamily PLATFORM_AF_E164 = (AIFamily)PLATFORM_AF_ISDN; // CCITT E.164 recommendation +const AIFamily PLATFORM_AF_NATM = (AIFamily)27; // native ATM access +const AIFamily PLATFORM_AF_ARP = (AIFamily)28; // (rev.) addr. res. prot. (RFC 826) +const AIFamily PLATFORM_PSEUDO_AF_KEY = (AIFamily)29; // Internal key management protocol +const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = (AIFamily)30; /* Used by BPF to not rewrite hdrs in interface output routine */ +const AIFamily PLATFORM_AF_BLUETOOTH = (AIFamily)31; // Bluetooth: HCI, SCO, L2CAP, RFCOMM +const AIFamily PLATFORM_AF_IEEE80211 = (AIFamily)32; // IEEE80211 +const AIFamily PLATFORM_AF_MPLS = (AIFamily)33; // MultiProtocol Label Switching +const AIFamily PLATFORM_AF_ROUTE = (AIFamily)34; // Internal Routing Protocol +const AIFamily PLATFORM_AF_CAN = (AIFamily)35; +const AIFamily PLATFORM_AF_ETHER = (AIFamily)36; +const AIFamily PLATFORM_AF_MAX = (AIFamily)37; const int SOL_SOCKET = 0xFFFF; diff --git a/test/stdlib/src/net/os/openbsd.c3 b/test/stdlib/src/net/os/openbsd.c3 index ddae8a7..ed6f080 100644 --- a/test/stdlib/src/net/os/openbsd.c3 +++ b/test/stdlib/src/net/os/openbsd.c3 @@ -2,49 +2,49 @@ module std::net::os @if(env::OPENBSD); import libc; // for most of this, see OpenBSD /usr/include/sys/socket.h -const AIFlags AI_EXT = 0x8; -const AIFlags AI_NUMERICSERV = 0x10; -const AIFlags AI_FQDN = 0x20; -const AIFlags AI_ADDRCONFIG = 0x40; +const AIFlags AI_EXT = (AIFlags)0x8; +const AIFlags AI_NUMERICSERV = (AIFlags)0x10; +const AIFlags AI_FQDN = (AIFlags)0x20; +const AIFlags AI_ADDRCONFIG = (AIFlags)0x40; -const AIFamily PLATFORM_AF_LOCAL = AF_UNIX; // draft POSIX compatibility -const AIFamily PLATFORM_AF_IMPLINK = 3; // arpanet imp addresses -const AIFamily PLATFORM_AF_PUP = 4; // pup protocols: e.g. BSP -const AIFamily PLATFORM_AF_CHAOS = 5; // mit CHAOS protocols -const AIFamily PLATFORM_AF_NS = 6; // XEROX NS protocols -const AIFamily PLATFORM_AF_ISO = 7; // ISO protocols -const AIFamily PLATFORM_AF_OSI = PLATFORM_AF_ISO; -const AIFamily PLATFORM_AF_ECMA = 8; // european computer manufacturers -const AIFamily PLATFORM_AF_DATAKIT = 9; // datakit protocols -const AIFamily PLATFORM_AF_CCITT = 10; // CCITT protocols, X.25 etc -const AIFamily PLATFORM_AF_SNA = 11; // IBM SNA -const AIFamily PLATFORM_AF_DECNET = 12; // DECnet -const AIFamily PLATFORM_AF_DLI = 13; // DEC Direct data link interface -const AIFamily PLATFORM_AF_LAT = 14; // LAT -const AIFamily PLATFORM_AF_HYLINK = 15; // NSC Hyperchannel -const AIFamily PLATFORM_AF_APPLETALK = 16; // Apple Talk -const AIFamily PLATFORM_AF_ROUTE = 17; // Internal Routing Protocol -const AIFamily PLATFORM_AF_LINK = 18; // Link layer interface -const AIFamily PLATFORM_PSEUDO_AF_XTP = 19; // eXpress Transfer Protocol (no AF) -const AIFamily PLATFORM_AF_COIP = 20; // connection-oriented IP, aka ST II -const AIFamily PLATFORM_AF_CNT = 21; // Computer Network Technology -const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22; // Help Identify RTIP packets -const AIFamily PLATFORM_AF_IPX = 23; // Novell Internet Protocol -const AIFamily PLATFORM_AF_INET6 = 24; // IPv6 -const AIFamily PLATFORM_PSEUDO_AF_PIP = 25; // Help Identify PIP packets -const AIFamily PLATFORM_AF_ISDN = 26; // Integrated Services Digital Network*/ -const AIFamily PLATFORM_AF_E164 = PLATFORM_AF_ISDN; // CCITT E.164 recommendation -const AIFamily PLATFORM_AF_NATM = 27; // native ATM access -const AIFamily PLATFORM_AF_ENCAP = 28; -const AIFamily PLATFORM_AF_SIP = 29; // Simple Internet Protocol -const AIFamily PLATFORM_AF_KEY = 30; -const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 31; // Used by BPF to not rewrite headers in interface output routine -const AIFamily PLATFORM_AF_BLUETOOTH = 32; // Bluetooth -const AIFamily PLATFORM_AF_MPLS = 33; // MPLS -const AIFamily PLATFORM_PSEUDO_AF_PFLOW = 34; // pflow -const AIFamily PLATFORM_PSEUDO_AF_PIPEX = 35; // PIPEX -const AIFamily PLATFORM_AF_FRAME = 36; // frame (Ethernet) sockets -const AIFamily PLATFORM_AF_MAX = 37; +const AIFamily PLATFORM_AF_LOCAL = (AIFamily)AF_UNIX; // draft POSIX compatibility +const AIFamily PLATFORM_AF_IMPLINK = (AIFamily)3; // arpanet imp addresses +const AIFamily PLATFORM_AF_PUP = (AIFamily)4; // pup protocols: e.g. BSP +const AIFamily PLATFORM_AF_CHAOS = (AIFamily)5; // mit CHAOS protocols +const AIFamily PLATFORM_AF_NS = (AIFamily)6; // XEROX NS protocols +const AIFamily PLATFORM_AF_ISO = (AIFamily)7; // ISO protocols +const AIFamily PLATFORM_AF_OSI = (AIFamily)PLATFORM_AF_ISO; +const AIFamily PLATFORM_AF_ECMA = (AIFamily)8; // european computer manufacturers +const AIFamily PLATFORM_AF_DATAKIT = (AIFamily)9; // datakit protocols +const AIFamily PLATFORM_AF_CCITT = (AIFamily)10; // CCITT protocols, X.25 etc +const AIFamily PLATFORM_AF_SNA = (AIFamily)11; // IBM SNA +const AIFamily PLATFORM_AF_DECNET = (AIFamily)12; // DECnet +const AIFamily PLATFORM_AF_DLI = (AIFamily)13; // DEC Direct data link interface +const AIFamily PLATFORM_AF_LAT = (AIFamily)14; // LAT +const AIFamily PLATFORM_AF_HYLINK = (AIFamily)15; // NSC Hyperchannel +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)16; // Apple Talk +const AIFamily PLATFORM_AF_ROUTE = (AIFamily)17; // Internal Routing Protocol +const AIFamily PLATFORM_AF_LINK = (AIFamily)18; // Link layer interface +const AIFamily PLATFORM_PSEUDO_AF_XTP = (AIFamily)19; // eXpress Transfer Protocol (no AF) +const AIFamily PLATFORM_AF_COIP = (AIFamily)20; // connection-oriented IP, aka ST II +const AIFamily PLATFORM_AF_CNT = (AIFamily)21; // Computer Network Technology +const AIFamily PLATFORM_PSEUDO_AF_RTIP = (AIFamily)22; // Help Identify RTIP packets +const AIFamily PLATFORM_AF_IPX = (AIFamily)23; // Novell Internet Protocol +const AIFamily PLATFORM_AF_INET6 = (AIFamily)24; // IPv6 +const AIFamily PLATFORM_PSEUDO_AF_PIP = (AIFamily)25; // Help Identify PIP packets +const AIFamily PLATFORM_AF_ISDN = (AIFamily)26; // Integrated Services Digital Network*/ +const AIFamily PLATFORM_AF_E164 = (AIFamily)PLATFORM_AF_ISDN; // CCITT E.164 recommendation +const AIFamily PLATFORM_AF_NATM = (AIFamily)27; // native ATM access +const AIFamily PLATFORM_AF_ENCAP = (AIFamily)28; +const AIFamily PLATFORM_AF_SIP = (AIFamily)29; // Simple Internet Protocol +const AIFamily PLATFORM_AF_KEY = (AIFamily)30; +const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = (AIFamily)31; // Used by BPF to not rewrite headers in interface output routine +const AIFamily PLATFORM_AF_BLUETOOTH = (AIFamily)32; // Bluetooth +const AIFamily PLATFORM_AF_MPLS = (AIFamily)33; // MPLS +const AIFamily PLATFORM_PSEUDO_AF_PFLOW = (AIFamily)34; // pflow +const AIFamily PLATFORM_PSEUDO_AF_PIPEX = (AIFamily)35; // PIPEX +const AIFamily PLATFORM_AF_FRAME = (AIFamily)36; // frame (Ethernet) sockets +const AIFamily PLATFORM_AF_MAX = (AIFamily)37; // see: https://github.com/openbsd/src/blob/1ca4dc792538d17b4c2bd71550f68070505fd7b9/sys/sys/socket.h#L157 const int SOL_SOCKET = 0xFFFF; diff --git a/test/stdlib/src/net/os/posix.c3 b/test/stdlib/src/net/os/posix.c3 index 3d77d3d..79de3b2 100644 --- a/test/stdlib/src/net/os/posix.c3 +++ b/test/stdlib/src/net/os/posix.c3 @@ -35,19 +35,19 @@ fn fault convert_error(Errno error) { switch (error) { - case errno::EACCES: return io::NO_PERMISSION; - case errno::EADDRINUSE: return net::ADDRESS_IN_USE; - case errno::EALREADY: return net::CONNECTION_ALREADY_IN_PROGRESS; - case errno::EBADF: return net::BAD_SOCKET_DESCRIPTOR; - case errno::ECONNREFUSED: return net::CONNECTION_REFUSED; - case errno::ECONNRESET: return net::CONNECTION_RESET; - case errno::EISCONN: return net::ALREADY_CONNECTED; - case errno::ENETUNREACH: return net::NETWORK_UNREACHABLE; - case errno::ENOTSOCK: return net::NOT_A_SOCKET; - case errno::EINTR: return io::INTERRUPTED; - case errno::EWOULDBLOCK: return io::WOULD_BLOCK; - case errno::EOPNOTSUPP: return net::OPERATION_NOT_SUPPORTED_ON_SOCKET; - case errno::ETIMEDOUT: return net::CONNECTION_TIMED_OUT; + case EACCES: return io::NO_PERMISSION; + case EADDRINUSE: return net::ADDRESS_IN_USE; + case EALREADY: return net::CONNECTION_ALREADY_IN_PROGRESS; + case EBADF: return net::BAD_SOCKET_DESCRIPTOR; + case ECONNREFUSED: return net::CONNECTION_REFUSED; + case ECONNRESET: return net::CONNECTION_RESET; + case EISCONN: return net::ALREADY_CONNECTED; + case ENETUNREACH: return net::NETWORK_UNREACHABLE; + case ENOTSOCK: return net::NOT_A_SOCKET; + case EINTR: return io::INTERRUPTED; + case EWOULDBLOCK: return io::WOULD_BLOCK; + case EOPNOTSUPP: return net::OPERATION_NOT_SUPPORTED_ON_SOCKET; + case ETIMEDOUT: return net::CONNECTION_TIMED_OUT; default: return io::GENERAL_ERROR; } } @@ -66,7 +66,7 @@ macro void? NativeSocket.close(self) { if (libc::close(self)) { - if (libc::errno() == errno::EBADF) return net::INVALID_SOCKET~; + if (libc::errno() == EBADF) return net::INVALID_SOCKET~; return net::GENERAL_ERROR~; } } @@ -86,7 +86,7 @@ macro void? NativeSocket.set_non_blocking(self, bool non_blocking) } if (fcntl(self, F_SETFL, flags) == -1) { - if (libc::errno() == errno::EBADF) return net::INVALID_SOCKET~; + if (libc::errno() == EBADF) return net::INVALID_SOCKET~; return net::GENERAL_ERROR~; } } diff --git a/test/stdlib/src/net/os/win32.c3 b/test/stdlib/src/net/os/win32.c3 index 0ddae74..bf9e4da 100644 --- a/test/stdlib/src/net/os/win32.c3 +++ b/test/stdlib/src/net/os/win32.c3 @@ -3,14 +3,12 @@ import std::os, std::io, libc, std::thread; import std::core::mem; import std::os::win32; - - -const AIFamily PLATFORM_AF_IPX = 6; -const AIFamily PLATFORM_AF_APPLETALK = 16; -const AIFamily PLATFORM_AF_NETBIOS = 17; -const AIFamily PLATFORM_AF_INET6 = 23; -const AIFamily PLATFORM_AF_IRDA = 26; -const AIFamily PLATFORM_AF_BTH = 32; +const AIFamily PLATFORM_AF_IPX = (AIFamily)6; +const AIFamily PLATFORM_AF_APPLETALK = (AIFamily)16; +const AIFamily PLATFORM_AF_NETBIOS = (AIFamily)17; +const AIFamily PLATFORM_AF_INET6 = (AIFamily)23; +const AIFamily PLATFORM_AF_IRDA = (AIFamily)26; +const AIFamily PLATFORM_AF_BTH = (AIFamily)32; const int FIONREAD = 1074030207; const int FIONBIO = -2147195266; @@ -96,23 +94,23 @@ fn fault convert_error(WSAError error) { switch (error) { - case wsa::NOTINITIALISED: return net::SOCKETS_NOT_INITIALIZED; - case wsa::ENETDOWN: return net::NETWORK_UNREACHABLE; - case wsa::INVALID_HANDLE: return net::BAD_SOCKET_DESCRIPTOR; - case wsa::EACCESS: return io::NO_PERMISSION; - case wsa::EINPROGRESS: return net::STILL_PROCESSING_CALLBACK; - case wsa::EADDRINUSE: return net::ADDRESS_IN_USE; - case wsa::EALREADY: return net::CONNECTION_ALREADY_IN_PROGRESS; - case wsa::EBADF: return net::BAD_SOCKET_DESCRIPTOR; - case wsa::EINTR: return io::INTERRUPTED; - case wsa::EWOULDBLOCK: return io::WOULD_BLOCK; - case wsa::ECONNREFUSED: return net::CONNECTION_REFUSED; - case wsa::EISCONN: return net::ALREADY_CONNECTED; - case wsa::ENETUNREACH: return net::NETWORK_UNREACHABLE; - case wsa::ENOTSOCK: return net::NOT_A_SOCKET; - case wsa::EOPNOTSUPP: return net::OPERATION_NOT_SUPPORTED_ON_SOCKET; - case wsa::ETIMEDOUT: return net::CONNECTION_TIMED_OUT; - case wsa::ECONNRESET: return net::CONNECTION_RESET; + case NOTINITIALISED: return net::SOCKETS_NOT_INITIALIZED; + case ENETDOWN: return net::NETWORK_UNREACHABLE; + case INVALID_HANDLE: return net::BAD_SOCKET_DESCRIPTOR; + case EACCESS: return io::NO_PERMISSION; + case EINPROGRESS: return net::STILL_PROCESSING_CALLBACK; + case EADDRINUSE: return net::ADDRESS_IN_USE; + case EALREADY: return net::CONNECTION_ALREADY_IN_PROGRESS; + case EBADF: return net::BAD_SOCKET_DESCRIPTOR; + case EINTR: return io::INTERRUPTED; + case EWOULDBLOCK: return io::WOULD_BLOCK; + case ECONNREFUSED: return net::CONNECTION_REFUSED; + case EISCONN: return net::ALREADY_CONNECTED; + case ENETUNREACH: return net::NETWORK_UNREACHABLE; + case ENOTSOCK: return net::NOT_A_SOCKET; + case EOPNOTSUPP: return net::OPERATION_NOT_SUPPORTED_ON_SOCKET; + case ETIMEDOUT: return net::CONNECTION_TIMED_OUT; + case ECONNRESET: return net::CONNECTION_RESET; default: return io::GENERAL_ERROR; } } diff --git a/test/stdlib/src/net/socket.c3 b/test/stdlib/src/net/socket.c3 index 6b68f57..cd33872 100644 --- a/test/stdlib/src/net/socket.c3 +++ b/test/stdlib/src/net/socket.c3 @@ -4,10 +4,10 @@ import std::io, std::os, std::time, libc; struct Socket (InStream, OutStream) { NativeSocket sock; - Socklen_t ai_addrlen; + Socklen_t addrlen; // TODO proper way to get the size of sockaddr_storage // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms740504(v=vs.85) - char[128] ai_addr_storage; + char[128] addr_storage; } macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai)) @@ -36,14 +36,6 @@ constdef PollSubscribe : ushort WRITE = os::POLLWRNORM, } -const PollSubscribe SUBSCRIBE_ANY_READ = (PollSubscribe)os::POLLIN; -const PollSubscribe SUBSCRIBE_PRIO_READ = (PollSubscribe)os::POLLPRI; -const PollSubscribe SUBSCRIBE_OOB_READ = (PollSubscribe)os::POLLRDBAND; -const PollSubscribe SUBSCRIBE_READ = (PollSubscribe)os::POLLRDNORM; -const PollSubscribe SUBSCRIBE_ANY_WRITE = (PollSubscribe)os::POLLOUT; -const PollSubscribe SUBSCRIBE_OOB_WRITE = (PollSubscribe)os::POLLWRBAND; -const PollSubscribe SUBSCRIBE_WRITE = (PollSubscribe)os::POLLWRNORM; - constdef PollEvent : ushort { READ_PRIO = os::POLLPRI, @@ -56,18 +48,6 @@ constdef PollEvent : ushort INVALID = os::POLLNVAL, } -const PollEvent POLL_EVENT_READ_PRIO = (PollEvent)os::POLLPRI; -const PollEvent POLL_EVENT_READ_OOB = (PollEvent)os::POLLRDBAND; -const PollEvent POLL_EVENT_READ = (PollEvent)os::POLLRDNORM; -const PollEvent POLL_EVENT_WRITE_OOB = (PollEvent)os::POLLWRBAND; -const PollEvent POLL_EVENT_WRITE = (PollEvent)os::POLLWRNORM; -const PollEvent POLL_EVENT_DISCONNECT = (PollEvent)os::POLLHUP; -const PollEvent POLL_EVENT_ERROR = (PollEvent)os::POLLERR; -const PollEvent POLL_EVENT_INVALID = (PollEvent)os::POLLNVAL; - -alias PollSubscribes @deprecated("Use PollSubscribe") = PollSubscribe; -alias PollEvents @deprecated("Use PollEvent") = PollEvent; - struct Poll { NativeSocket socket; @@ -77,7 +57,7 @@ struct Poll <* @param [inout] polls - @param timeout : "duration to poll (clamped to CInt.max ms), or POLL_FOREVER." + @param timeout : "duration to poll (clamped to Cint::max ms), or POLL_FOREVER." *> fn ulong? poll(Poll[] polls, Duration timeout) { @@ -86,11 +66,11 @@ fn ulong? poll(Poll[] polls, Duration timeout) <* @param [inout] polls - @param timeout_ms : "duration to poll in ms or -1. Clamped to CInt.max" + @param timeout_ms : "duration to poll in ms or -1. Clamped to Cint::max" *> fn ulong? poll_ms(Poll[] polls, long timeout_ms) { - if (timeout_ms > CInt.max) timeout_ms = CInt.max; + if (timeout_ms > CInt::max) timeout_ms = CInt::max; $if env::WIN32: CInt result = win32::wsaPoll((Win32_LPWSAPOLLFD)polls.ptr, (Win32_ULONG)polls.len, (CInt)timeout_ms); $else @@ -101,9 +81,9 @@ fn ulong? poll_ms(Poll[] polls, long timeout_ms) macro Socket new_socket(fd, ai) { - Socket sock = { .sock = fd, .ai_addrlen = ai.ai_addrlen }; - assert(sock.ai_addr_storage.len >= ai.ai_addrlen, "storage %d < addrlen %d", sock.ai_addr_storage.len, ai.ai_addrlen); - mem::copy(&sock.ai_addr_storage, (void*)ai.ai_addr, ai.ai_addrlen); + Socket sock = { .sock = fd, .addrlen = ai.ai_addrlen }; + assert(sock.addr_storage.len >= ai.ai_addrlen, "storage %d < addrlen %d", sock.addr_storage.len, ai.ai_addrlen); + mem::copy(&sock.addr_storage, (void*)ai.ai_addr, (sz)ai.ai_addrlen); return sock; } @@ -132,41 +112,41 @@ fn void? Socket.set_oobinline(&self, bool value) => self.set_option(OOBINLINE, v fn void? Socket.set_option(&self, SocketOption option, bool value) { CInt flag = (CInt)value; - int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt.sizeof); + int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt::size); if (errcode != 0) return SOCKOPT_FAILED~; } fn bool? Socket.get_option(&self, SocketOption option) { CInt flag; - Socklen_t socklen = CInt.sizeof; + Socklen_t socklen = CInt::size; int errcode = os::getsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, &socklen); if (errcode != 0) return SOCKOPT_FAILED~; return (bool)flag; } -fn usz? Socket.read(&self, char[] bytes) @dynamic +fn sz? Socket.read(&self, char[] bytes) @dynamic { $if env::WIN32: - isz n = libc::recv(self.sock, bytes.ptr, (int)bytes.len, 0); + sz n = libc::recv(self.sock, bytes.ptr, (int)bytes.len, 0); $else - isz n = libc::recv(self.sock, bytes.ptr, bytes.len, 0); + sz n = libc::recv(self.sock, bytes.ptr, (usz)bytes.len, 0); $endif if (n < 0) return os::socket_error()~; - return (usz)n; + return (sz)n; } fn char? Socket.read_byte(&self) @dynamic => io::read_byte_using_read(self); -fn usz? Socket.write(&self, char[] bytes) @dynamic +fn sz? Socket.write(&self, char[] bytes) @dynamic { $if env::WIN32: - isz n = libc::send(self.sock, bytes.ptr, (int)bytes.len, 0); + sz n = libc::send(self.sock, bytes.ptr, (int)bytes.len, 0); $else - isz n = libc::send(self.sock, bytes.ptr, bytes.len, 0); + sz n = libc::send(self.sock, bytes.ptr, (usz)bytes.len, 0); $endif if (n < 0) return os::socket_error()~; - return (usz)n; + return (sz)n; } fn void? Socket.write_byte(&self, char byte) @dynamic => io::write_byte_using_write(self, byte); @@ -180,15 +160,15 @@ fn void? Socket.close(&self) @inline @dynamic self.sock.close()!; } -fn usz? Socket.peek(&self, char[] bytes) @dynamic +fn sz? Socket.peek(&self, char[] bytes) @dynamic { $if env::WIN32: - isz n = libc::recv(self.sock, bytes.ptr, (int)bytes.len, os::MSG_PEEK); + sz n = libc::recv(self.sock, bytes.ptr, (int)bytes.len, os::MSG_PEEK); $else - isz n = libc::recv(self.sock, bytes.ptr, bytes.len, os::MSG_PEEK); + sz n = libc::recv(self.sock, bytes.ptr, (usz)bytes.len, os::MSG_PEEK); $endif if (n < 0) return os::socket_error()~; - return (usz)n; + return (sz)n; } enum SocketShutdownHow : (CInt native_value) diff --git a/test/stdlib/src/net/socket_private.c3 b/test/stdlib/src/net/socket_private.c3 index 87ca95f..7e481aa 100644 --- a/test/stdlib/src/net/socket_private.c3 +++ b/test/stdlib/src/net/socket_private.c3 @@ -32,13 +32,13 @@ fn bool last_error_is_delayed_connect() $case env::WIN32: switch (win32::wsaGetLastError()) { - case wsa::EWOULDBLOCK: - case wsa::EINPROGRESS: return true; + case EWOULDBLOCK: + case EINPROGRESS: return true; default: return false; } $default: Errno err = libc::errno(); - return err == errno::EINPROGRESS || err == errno::EAGAIN || err == errno::EWOULDBLOCK; + return err == EINPROGRESS || err == EAGAIN || err == EWOULDBLOCK; $endswitch } @@ -75,12 +75,12 @@ fn Socket? connect_with_timeout_from_addrinfo(AddrInfo* addrinfo, SocketOption[] { c = clock::now(); } - Poll poll_request = { sockfd, SUBSCRIBE_ANY_WRITE, (PollEvent)0 }; + Poll poll_request = { sockfd, ANY_WRITE, (PollEvent)0 }; if (!poll((&poll_request)[:1], timeout_left)!) { return CONNECTION_TIMED_OUT~; } - if (poll_request.revents & POLL_EVENT_WRITE) + if (poll_request.revents & WRITE) { sockfd.set_non_blocking(false)!; return new_socket(sockfd, ai); diff --git a/test/stdlib/src/net/tcp.c3 b/test/stdlib/src/net/tcp.c3 index 9c17bea..5674558 100644 --- a/test/stdlib/src/net/tcp.c3 +++ b/test/stdlib/src/net/tcp.c3 @@ -10,7 +10,7 @@ import std::net::os; typedef TcpSocket = inline Socket; typedef TcpServerSocket = inline Socket; -fn TcpSocket? connect(String host, uint port, Duration timeout = time::DURATION_ZERO, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn TcpSocket? connect(String host, int port, Duration timeout = time::DURATION_ZERO, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!; defer os::freeaddrinfo(ai); @@ -21,7 +21,7 @@ fn TcpSocket? connect(String host, uint port, Duration timeout = time::DURATION_ return connect_to(ai, ...options); } -fn TcpSocket? connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn TcpSocket? connect_async(String host, int port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!; defer os::freeaddrinfo(ai); @@ -38,7 +38,7 @@ fn TcpSocket? connect_async_to(AddrInfo* ai, SocketOption... options) return (TcpSocket)net::connect_async_from_addrinfo(ai, options); } -fn TcpServerSocket? listen(String host, uint port, uint backlog, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn TcpServerSocket? listen(String host, int port, int backlog, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!; defer os::freeaddrinfo(ai); @@ -48,16 +48,16 @@ fn TcpServerSocket? listen(String host, uint port, uint backlog, SocketOption... fn TcpSocket? accept(TcpServerSocket* server_socket) { TcpSocket socket; - socket.ai_addrlen = socket.ai_addr_storage.len; + socket.addrlen = socket.addr_storage.len; $if env::WIN32: os::start_wsa()!; $endif - socket.sock = os::accept(server_socket.sock, (SockAddrPtr)&socket.ai_addr_storage, &socket.ai_addrlen); + socket.sock = os::accept(server_socket.sock, (SockAddrPtr)&socket.addr_storage, &socket.addrlen); if (!socket.sock.is_valid()) return net::ACCEPT_FAILED~; return socket; } -fn TcpServerSocket? listen_to(AddrInfo* ai, uint backlog, SocketOption... options) +fn TcpServerSocket? listen_to(AddrInfo* ai, int backlog, SocketOption... options) { $if env::WIN32: os::start_wsa()!; @@ -85,15 +85,15 @@ fn TcpSocketPair*? TcpSocketPair.init(&self) TcpServerSocket listen_sock = tcp::listen("127.0.0.1", 0, 0)!; TcpSocket listen_sock_info; - listen_sock_info.ai_addrlen = listen_sock.ai_addr_storage.len; + listen_sock_info.addrlen = listen_sock.addr_storage.len; - int sock_result = os::getsockname(listen_sock.sock, (SockAddrPtr) &listen_sock_info.ai_addr_storage, &listen_sock_info.ai_addrlen); + int sock_result = os::getsockname(listen_sock.sock, (SockAddrPtr) &listen_sock_info.addr_storage, &listen_sock_info.addrlen); if (sock_result < 0) return os::socket_error()~; - char[] listen_port_bytes = listen_sock_info.ai_addr_storage[2:2]; + char[] listen_port_bytes = listen_sock_info.addr_storage[2:2]; char msb = listen_port_bytes[0]; char lsb = listen_port_bytes[1]; - int listen_port = (msb << 8) | lsb; + int listen_port = ((int)msb << 8) | lsb; defer (void)listen_sock.close(); TcpSocket tcp_send_sock = tcp::connect_async("127.0.0.1", listen_port)!; @@ -101,7 +101,7 @@ fn TcpSocketPair*? TcpSocketPair.init(&self) $else NativeSocket[2] sockets; - isz sockpair_result = os::socketpair(os::AF_UNIX, os::SOCK_STREAM, 0, &sockets); + sz sockpair_result = os::socketpair(os::AF_UNIX, os::SOCK_STREAM, 0, &sockets); if (sockpair_result < 0) return os::socket_error()~; Socket send_sock = { .sock = sockets[0] }; diff --git a/test/stdlib/src/net/udp.c3 b/test/stdlib/src/net/udp.c3 index 1f7b96e..ed96fe6 100644 --- a/test/stdlib/src/net/udp.c3 +++ b/test/stdlib/src/net/udp.c3 @@ -3,7 +3,7 @@ import std::net @public; typedef UdpSocket = inline Socket; -fn UdpSocket? connect(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn UdpSocket? connect(String host, int port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_DGRAM)!; defer os::freeaddrinfo(ai); @@ -15,7 +15,7 @@ fn UdpSocket? connect_to(AddrInfo* ai, SocketOption... options) return (UdpSocket)net::connect_from_addrinfo(ai, options); } -fn UdpSocket? connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn UdpSocket? connect_async(String host, int port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_DGRAM)!; defer os::freeaddrinfo(ai); diff --git a/test/stdlib/src/net/url.c3 b/test/stdlib/src/net/url.c3 index 089a600..a551833 100644 --- a/test/stdlib/src/net/url.c3 +++ b/test/stdlib/src/net/url.c3 @@ -30,7 +30,7 @@ struct Url(Printable) { String scheme; String host; - uint port; + int port; String username; String password; String path; @@ -81,7 +81,7 @@ fn Url? parse(Allocator allocator, String url_string) // Parse host, port if (url.scheme != "urn") { - usz authority_end = url_string.index_of_chars("/?#") ?? url_string.len; + sz authority_end = url_string.index_of_chars("/?#") ?? url_string.len; String authority = url_string[:authority_end]; if (try user_info_end = authority.index_of_char('@')) @@ -106,11 +106,11 @@ fn Url? parse(Allocator allocator, String url_string) String host; if (authority.starts_with("[") && authority.contains("]")) { - usz ipv6_end = authority.index_of("]")!; + sz ipv6_end = authority.index_of("]")!; host = authority[0 .. ipv6_end]; // Includes closing bracket if ((ipv6_end + 1) < authority.len && authority[.. ipv6_end] == ":") { - url.port = authority[.. ipv6_end + 1].to_uint()!; + url.port = authority[.. ipv6_end + 1].to_int()!; } } else @@ -121,7 +121,7 @@ fn Url? parse(Allocator allocator, String url_string) if (host_port.len > 1) { host = host_port[0]; - url.port = host_port[1].to_uint()!; + url.port = host_port[1].to_int()!; } else { @@ -134,12 +134,12 @@ fn Url? parse(Allocator allocator, String url_string) } // Parse path - usz? query_index = url_string.index_of_char('?'); - usz? fragment_index = url_string.index_of_char('#'); + sz? query_index = url_string.index_of_char('?'); + sz? fragment_index = url_string.index_of_char('#'); if (@ok(query_index) || @ok(fragment_index)) { - usz path_end = min(query_index ?? url_string.len, fragment_index ?? url_string.len); + sz path_end = min(query_index ?? url_string.len, fragment_index ?? url_string.len); url.path = decode(allocator, url_string[:path_end], PATH) ?? INVALID_PATH~!; url_string = url_string[path_end ..]; } @@ -155,7 +155,7 @@ fn Url? parse(Allocator allocator, String url_string) // Parse query if (url_string.starts_with("?")) { - usz index = url_string.index_of_char('#') ?? url_string.len; + sz index = url_string.index_of_char('#') ?? url_string.len; url.query = url_string[1 .. index - 1].copy(allocator); url_string = url_string[index ..]; } @@ -168,9 +168,9 @@ fn Url? parse(Allocator allocator, String url_string) return url; } -fn usz? Url.to_format(&self, Formatter* f) @dynamic +fn sz? Url.to_format(&self, Formatter* f) @dynamic { - usz len; + sz len; // Add scheme if it exists if (self.scheme != "") { @@ -308,10 +308,10 @@ fn UrlQueryValues* UrlQueryValues.add(&self, String key, String value) -fn usz? UrlQueryValues.to_format(&self, Formatter* f) @dynamic +fn sz? UrlQueryValues.to_format(&self, Formatter* f) @dynamic { - usz len; - usz i; + sz len; + sz i; foreach (key: self.key_order) { @stack_mem(128; Allocator mem) diff --git a/test/stdlib/src/net/url_encoding.c3 b/test/stdlib/src/net/url_encoding.c3 index c4d2a46..4f3025d 100644 --- a/test/stdlib/src/net/url_encoding.c3 +++ b/test/stdlib/src/net/url_encoding.c3 @@ -41,9 +41,9 @@ fn bool should_encode(char c, UrlEncodingMode mode) @private <* Calculate the length of the percent-encoded string. *> -fn usz encode_len(String s, UrlEncodingMode mode) @inline +fn sz encode_len(String s, UrlEncodingMode mode) @inline { - usz n; + sz n; foreach (c: s) { if (!should_encode(c, mode)) continue; @@ -66,7 +66,7 @@ fn usz encode_len(String s, UrlEncodingMode mode) @inline *> fn String encode(Allocator allocator, String s, UrlEncodingMode mode) => @pool() { - usz n = encode_len(s, mode); + sz n = encode_len(s, mode); DString builder = dstring::temp_with_capacity(n); foreach(i, c: s) @@ -107,9 +107,9 @@ fn String tencode(String s, UrlEncodingMode mode) => encode(tmem, s, mode); @return? INVALID_HEX *> -fn usz? decode_len(String s, UrlEncodingMode mode) @inline +fn sz? decode_len(String s, UrlEncodingMode mode) @inline { - usz n; + sz n; foreach (i, c: s) { if (c != '%') continue; @@ -133,10 +133,10 @@ fn usz? decode_len(String s, UrlEncodingMode mode) @inline *> fn String? decode(Allocator allocator, String s, UrlEncodingMode mode) => @pool() { - usz n = decode_len(s, mode)!; + sz n = decode_len(s, mode)!; DString builder = dstring::temp_with_capacity(n); - for (usz i = 0; i < s.len; i++) + for (sz i = 0; i < s.len; i++) { switch (s[i]) { diff --git a/test/stdlib/src/os/android/log.c3 b/test/stdlib/src/os/android/log.c3 index fb756e4..15dafc2 100644 --- a/test/stdlib/src/os/android/log.c3 +++ b/test/stdlib/src/os/android/log.c3 @@ -63,7 +63,7 @@ extern fn void log_set_aborter(AborterFunction aborter) @cname("__android_log_se extern fn void log_call_aborter(ZString abort_message) @cname("__android_log_call_aborter"); extern fn void log_default_aborter(ZString abort_message) @cname("__android_log_default_aborter"); extern fn CInt log_is_loggable(CInt prio, ZString tag, CInt default_prio) @cname("__android_log_is_loggable"); -extern fn CInt log_is_loggable_len(CInt prio, ZString tag, isz len, CInt default_prio) @cname("__android_log_is_loggable_len"); +extern fn CInt log_is_loggable_len(CInt prio, ZString tag, sz len, CInt default_prio) @cname("__android_log_is_loggable_len"); extern fn CInt log_set_minimum_priority(CInt priority) @cname("__android_log_set_minimum_priority"); extern fn CInt log_get_minimum_priority() @cname("__android_log_get_minimum_priority"); extern fn void log_set_default_tag(ZString tag) @cname("__android_log_set_default_tag"); diff --git a/test/stdlib/src/os/backtrace.c3 b/test/stdlib/src/os/backtrace.c3 index a477d5b..206ed80 100644 --- a/test/stdlib/src/os/backtrace.c3 +++ b/test/stdlib/src/os/backtrace.c3 @@ -1,5 +1,5 @@ module std::os::backtrace; -import std::collections::list, std::os, std::io; +import std::collections::list, std::core::dstring, std::os, std::io; faultdef SEGMENT_NOT_FOUND, EXECUTABLE_PATH_NOT_FOUND, IMAGE_NOT_FOUND, NO_BACKTRACE_SYMBOLS, RESOLUTION_FAILED; @@ -8,15 +8,24 @@ const Backtrace BACKTRACE_UNKNOWN = { 0, "", "", "", 0, null, false }; struct Backtrace (Printable) { - uptr offset; + sz offset; String function; String object_file; String file; - uint line; + int line; Allocator allocator; bool is_inline; } +fn bool Backtrace.equals(self, Backtrace other) @operator(==) +{ + return self.offset == other.offset + && self.function == other.function + && self.object_file == other.object_file + && self.file == other.file + && self.line == other.line + && self.is_inline == other.is_inline; +} fn bool Backtrace.has_file(&self) { @@ -28,7 +37,7 @@ fn bool Backtrace.is_unknown(&self) return !self.object_file.len; } -fn usz? Backtrace.to_format(&self, Formatter* formatter) @dynamic +fn sz? Backtrace.to_format(&self, Formatter* formatter) @dynamic { String inline_suffix = self.is_inline ? " [inline]" : ""; if (self.has_file()) @@ -44,12 +53,12 @@ fn usz? Backtrace.to_format(&self, Formatter* formatter) @dynamic fn void Backtrace.free(&self) { if (!self.allocator) return; - allocator::free(self.allocator, self.function); - allocator::free(self.allocator, self.object_file); - allocator::free(self.allocator, self.file); + alloc::free(self.allocator, self.function); + alloc::free(self.allocator, self.object_file); + alloc::free(self.allocator, self.file); } -fn Backtrace* Backtrace.init(&self, Allocator allocator, uptr offset, String function, String object_file, String file = "", uint line = 0) +fn Backtrace* Backtrace.init(&self, Allocator allocator, sz offset, String function, String object_file, String file = "", int line = 0) { if (!allocator) { @@ -78,7 +87,7 @@ fn void*[] capture_current(void*[] buffer) CInt len = posix::backtrace(buffer.ptr, buffer.len); return buffer[:len]; $case env::WIN32: - Win32_WORD len = win32::rtlCaptureStackBackTrace(0, buffer.len, buffer.ptr, null); + Win32_WORD len = win32::rtlCaptureStackBackTrace(0, (usz)buffer.len, buffer.ptr, null); return buffer[:len]; $default: return buffer[:0]; @@ -96,3 +105,26 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace) @i { return {}; } + +<* + Returns the symbolized backtrace as a string. +*> +fn String? get(Allocator allocator) +{ + void*[64] buffer; + void*[] bt = capture_current(buffer[..]); + @pool() + { + if (try list = symbolize_backtrace(tmem, bt)) + { + DString s; + s.init(allocator); + foreach (trace : list) + { + s.appendf("%s\n", trace); + } + return s.str_view(); + } + }; + return NOT_FOUND~; +} diff --git a/test/stdlib/src/os/cpu.c3 b/test/stdlib/src/os/cpu.c3 index 8e7ffb0..4e41fb8 100644 --- a/test/stdlib/src/os/cpu.c3 +++ b/test/stdlib/src/os/cpu.c3 @@ -3,7 +3,7 @@ module std::os @if(env::DARWIN); import std::os::macos; -fn uint num_cpu() +fn int num_cpu() { int[2] nm; usz len = 4; @@ -12,25 +12,25 @@ fn uint num_cpu() nm = { darwin::CTL_HW, darwin::HW_NCPU }; darwin::sysctl(&nm, 2, &count, &len, null, 0); if (count < 1) count = 1; - return count; + return (int)count; } module std::os @if(env::LINUX || env::ANDROID); import std::os::posix; -fn uint num_cpu() +fn int num_cpu() { - return posix::get_nprocs_conf(); + return (int)posix::get_nprocs_conf(); } module std::os @if(env::WIN32); import std::os::win32; -fn uint num_cpu() +fn int num_cpu() { Win32_SYSTEM_INFO info; win32::getSystemInfo(&info); - return info.dwNumberOfProcessors; + return (int)info.dwNumberOfProcessors; } module std::os @if(env::NETBSD); @@ -53,19 +53,38 @@ module std::os @if(env::OPENBSD); import std::os::openbsd; import libc; -fn uint num_cpu() +fn int num_cpu() { int[2] nm; usz len = 4; uint count; nm = { openbsd::CTL_HW, openbsd::HW_NCPUONLINE }; - if (libc::sysctl(&nm, 2, &count, &len, null, 0) == 0 && count >= 1) { + if (libc::sysctl(&nm, 2, &count, &len, null, 0) == 0 && count >= 1) + { return count; } nm = { openbsd::CTL_HW, openbsd::HW_NCPU }; libc::sysctl(&nm, 2, &count, &len, null, 0); if (count < 1) count = 1; - return count; + return (int)count; } + +module std::os @if(env::FREEBSD); +import std::os::freebsd; +import libc; + +fn int num_cpu() +{ + int[2] nm; + usz len = 4; + uint count; + + nm = { freebsd::CTL_HW, freebsd::HW_NCPU }; + libc::sysctl(&nm, 2, &count, &len, null, 0); + if (count < 1) count = 1; + return (int)count; +} + + diff --git a/test/stdlib/src/os/env.c3 b/test/stdlib/src/os/env.c3 index 580a43e..0aa4bfc 100644 --- a/test/stdlib/src/os/env.c3 +++ b/test/stdlib/src/os/env.c3 @@ -18,10 +18,10 @@ fn String? get_var(Allocator allocator, String name) => @pool() return val ? val.copy(allocator) : NOT_FOUND~; $case env::WIN32: // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getenvironmentvariable - const usz BUFSIZE = 1024; + const sz BUFSIZE = 1024; WString buff = (WString)tcalloc(BUFSIZE * 2 + 2); WString wstr = name.to_temp_wstring()!; - usz len = win32::getEnvironmentVariableW(wstr, buff, BUFSIZE); + sz len = (sz)win32::getEnvironmentVariableW(wstr, buff, BUFSIZE); if (len == 0) return NOT_FOUND~; if (len > BUFSIZE) { diff --git a/test/stdlib/src/os/freebsd/general.c3 b/test/stdlib/src/os/freebsd/general.c3 index 94e0587..5e9da58 100644 --- a/test/stdlib/src/os/freebsd/general.c3 +++ b/test/stdlib/src/os/freebsd/general.c3 @@ -1,2 +1,38 @@ module std::os::freebsd @if(env::FREEBSD); + + +// Top-level identifiers + +const CTL_SYSCTL = 0; // "magic" numbers +const CTL_KERN = 1; // "high kernel": proc, limits +const CTL_VM = 2; // virtual memory +const CTL_VFS = 3; // filesystem, mount type is next +const CTL_NET = 4; // network, see socket.h +const CTL_DEBUG = 5; // debugging parameters +const CTL_HW = 6; // generic cpu/io +const CTL_MACHDEP = 7; // machine dependent +const CTL_USER = 8; // user-level +const CTL_P1003_1B = 9; // POSIX 1003.1B + + + + + +// CTL_HW identifiers + +const HW_MACHINE = 1; // string: machine class +const HW_MODEL = 2; // string: specific machine model +const HW_NCPU = 3; // int: number of cpus +const HW_BYTEORDER = 4; // int: machine byte order +const HW_PHYSMEM = 5; // int: total memory +const HW_USERMEM = 6; // int: non-kernel memory +const HW_PAGESIZE = 7; // int: software page size +const HW_DISKNAMES = 8; // strings: disk drive names +const HW_DISKSTATS = 9; // struct: diskstats[] +const HW_FLOATINGPT = 10; // int: has hw floating point? +const HW_MACHINE_ARCH = 11; // string: machine architecture +const HW_REALMEM = 12; // int: 'real' memory + + + diff --git a/test/stdlib/src/os/linux/epoll.c3 b/test/stdlib/src/os/linux/epoll.c3 index dd15179..e799920 100644 --- a/test/stdlib/src/os/linux/epoll.c3 +++ b/test/stdlib/src/os/linux/epoll.c3 @@ -83,10 +83,10 @@ const IOC_IN = 0x80000000; /* copy in parameters */ const IOC_INOUT = (IOC_IN|IOC_OUT); macro ulong @ioctl_IO ($x,$y) @const => (IOC_VOID | (($x)<<8)|y); -macro ulong @ioctl_IOR ($x,$y,$Type) @const => (IOC_OUT |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y); -macro ulong @ioctl_IOW ($x,$y,$Type) @const => (IOC_IN |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y); +macro ulong @ioctl_IOR ($x,$y,$Type) @const => (IOC_OUT |((usz)($Type::size&IOCPARM_MASK)<<16)|(($x)<<8)|$y); +macro ulong @ioctl_IOW ($x,$y,$Type) @const => (IOC_IN |((usz)($Type::size&IOCPARM_MASK)<<16)|(($x)<<8)|$y); /* this should be _IORW, but stdio got there first */ -macro ulong @ioctl_IOWR ($x,$y,$Type) @const => (IOC_INOUT|(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y); +macro ulong @ioctl_IOWR ($x,$y,$Type) @const => (IOC_INOUT|((usz)($Type::size&IOCPARM_MASK)<<16)|(($x)<<8)|$y); macro ulong @ioctl_ION ($x,$y,$n) @const => (IOC_INOUT|((($n)&IOCPARM_MASK)<<16)|(($x)<<8)|y); // https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h diff --git a/test/stdlib/src/os/linux/linux.c3 b/test/stdlib/src/os/linux/linux.c3 index 3bf7577..7209dc3 100644 --- a/test/stdlib/src/os/linux/linux.c3 +++ b/test/stdlib/src/os/linux/linux.c3 @@ -30,7 +30,7 @@ extern fn ushort ntohs(ushort netshort); *> extern fn void bzero(char*, usz); -extern fn isz readlink(ZString path, char* buf, usz bufsize); +extern fn sz readlink(ZString path, char* buf, usz bufsize); const PT_PHDR = 6; const EI_NIDENT = 16; @@ -186,16 +186,16 @@ fn ulong? elf_module_image_base(String path) @local bool is_little_endian = file.read_byte()! == 1; // Actually, not supported. if (!is_little_endian) return backtrace::IMAGE_NOT_FOUND~; - file.set_cursor(0)!; + file.seek(0)!; if (is_64) { Elf64_Ehdr file_header; io::read_any(&file, &file_header)!; - if (file_header.e_ehsize != Elf64_Ehdr.sizeof) return backtrace::IMAGE_NOT_FOUND~; - for (isz i = 0; i < file_header.e_phnum; i++) + if (file_header.e_ehsize != Elf64_Ehdr::size) return backtrace::IMAGE_NOT_FOUND~; + for (sz i = 0; i < file_header.e_phnum; i++) { Elf64_Phdr header; - file.set_cursor(file_header.e_phoff + (long)file_header.e_phentsize * i)!; + file.seek((long)file_header.e_phoff + (long)file_header.e_phentsize * i)!; io::read_any(&file, &header)!; if (header.p_type == PT_PHDR) return header.p_vaddr - header.p_offset; } @@ -203,11 +203,11 @@ fn ulong? elf_module_image_base(String path) @local } Elf32_Ehdr file_header; io::read_any(&file, &file_header)!; - if (file_header.e_ehsize != Elf32_Ehdr.sizeof) return backtrace::IMAGE_NOT_FOUND~; - for (isz i = 0; i < file_header.e_phnum; i++) + if (file_header.e_ehsize != Elf32_Ehdr::size) return backtrace::IMAGE_NOT_FOUND~; + for (sz i = 0; i < file_header.e_phnum; i++) { Elf32_Phdr header; - file.set_cursor(file_header.e_phoff + (long)file_header.e_phentsize * i)!; + file.seek(file_header.e_phoff + (long)file_header.e_phentsize * i)!; io::read_any(&file, &header)!; if (header.p_type == PT_PHDR) return (ulong)header.p_vaddr - header.p_offset; } @@ -217,9 +217,9 @@ fn ulong? elf_module_image_base(String path) @local fn void? backtrace_add_from_exec(Allocator allocator, BacktraceList* list, void* addr) @local { char[1024] buf @noinit; - String exec_path = process::execute_stdout_to_buffer(&buf, {"realpath", "-e", string::bformat(&&(char[64]){}, "/proc/%d/exe", posix::getpid())})!; + String exec_path = process::run_capture_stdout(&buf, {"realpath", "-e", string::bformat(&&(char[64]){}, "/proc/%d/exe", posix::getpid())})!; String obj_name = exec_path.copy(allocator); - String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::bformat(&&(char[64]){}, "0x%x", addr)})!; + String addr2line = process::run_capture_stdout(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::bformat(&&(char[64]){}, "0x%x", addr)})!; return backtrace_add_addr2line(allocator, list, addr, addr2line, obj_name, "???"); } @@ -227,10 +227,10 @@ fn void? backtrace_add_from_dlinfo(Allocator allocator, BacktraceList* list, voi { char[1024] buf @noinit; - void* obj_addr = addr - (uptr)info.dli_fbase + (uptr)elf_module_image_base(info.dli_fname.str_view())!; + void* obj_addr = addr - (iptr)info.dli_fbase + (iptr)elf_module_image_base(info.dli_fname.str_view())!; ZString obj_path = info.dli_fname; String sname = info.dli_sname ? info.dli_sname.str_view() : "???"; - String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::bformat(&&(char[64]){}, "0x%x", obj_addr - 1)})!; + String addr2line = process::run_capture_stdout(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::bformat(&&(char[64]){}, "0x%x", obj_addr - 1)})!; return backtrace_add_addr2line(allocator, list, addr, addr2line, info.dli_fname.str_view(), sname); } @@ -239,13 +239,13 @@ fn Backtrace? backtrace_line_parse(Allocator allocator, String string, String ob Splitter s = string.trim().tokenize(" at "); String first = s.next() ?? NOT_FOUND~!; String second = s.next() ?? NOT_FOUND~!; - uint line = 0; + int line = 0; String source = ""; if (!second.contains("?") && second.contains(":")) { - usz index = second.rindex_of_char(':')!; + sz index = second.rindex_of_char(':')!; source = second[:index]; - line = second[index + 1..].to_uint()!; + line = second[index + 1..].to_int()!; } return { .function = first.copy(allocator), @@ -269,7 +269,7 @@ fn void? backtrace_add_addr2line(Allocator allocator, BacktraceList* list, void* list.push({ .function = func_name.copy(allocator), .object_file = obj_name.copy(allocator), - .offset = (uptr)addr, + .offset = (sz)addr, .file = "".copy(allocator), .line = 0, .allocator = allocator, diff --git a/test/stdlib/src/os/macos/core_foundation.c3 b/test/stdlib/src/os/macos/core_foundation.c3 index d09bf00..8132660 100644 --- a/test/stdlib/src/os/macos/core_foundation.c3 +++ b/test/stdlib/src/os/macos/core_foundation.c3 @@ -2,7 +2,7 @@ module std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.fr typedef CFType = void; typedef CFTypeRef = CFType*; -alias CFIndex = isz; +alias CFIndex = sz; typedef CFString = inline CFType; alias CFStringRef = CFString*; diff --git a/test/stdlib/src/os/macos/darwin.c3 b/test/stdlib/src/os/macos/darwin.c3 index 7cad18b..fbd684a 100644 --- a/test/stdlib/src/os/macos/darwin.c3 +++ b/test/stdlib/src/os/macos/darwin.c3 @@ -82,7 +82,7 @@ extern fn ulong mach_absolute_time(); fn String? executable_path() { static char[4096] path; - static uint len = 0; + static int len = 0; if (!len) { char[4096] buf; @@ -105,7 +105,7 @@ fn uptr? load_address() @local ZString image_name = darwin::_dyld_get_image_name(i); if (!image_name) continue; if (image_name.str_view() != path) continue; - return cmd.vmaddr + darwin::_dyld_get_image_vmaddr_slide(i); + return (uptr)(cmd.vmaddr + (usz)darwin::_dyld_get_image_vmaddr_slide(i)); } return backtrace::IMAGE_NOT_FOUND~; } @@ -115,7 +115,7 @@ fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void* if (buffer) { char[1024] buf; - String s = process::execute_stdout_to_buffer(&buf, + String s = process::run_capture_stdout(&buf, { "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l", string::bformat(&&(char[64]){}, "%p", load_address), string::bformat(&&(char[64]){}, "%p", buffer - 1), @@ -127,11 +127,11 @@ fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void* { String[] path_parts = parts[3].split(mem, ":"); return { - .offset = (uptr)buffer, + .offset = (sz)(iptr)buffer, .function = parts[0].copy(allocator), .object_file = parts[2][..^2].copy(allocator), .file = path_parts[0][1..].copy(allocator), - .line = path_parts[1][..^2].to_uint()!, + .line = path_parts[1][..^2].to_int()!, .allocator = allocator }; } @@ -140,7 +140,7 @@ fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void* Darwin_Dl_info info; if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN; return { - .offset = (uptr)buffer, + .offset = (sz)(iptr)buffer, .function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator), .object_file = info.dli_fname.copy(allocator), .file = "".copy(allocator), diff --git a/test/stdlib/src/os/macos/objc.c3 b/test/stdlib/src/os/macos/objc.c3 index cd1ff78..43786d4 100644 --- a/test/stdlib/src/os/macos/objc.c3 +++ b/test/stdlib/src/os/macos/objc.c3 @@ -75,18 +75,6 @@ extern fn void registerClassPair(ObjcClass cls) @cname("objc_registerClassPair") module std::os::macos::objc::ns @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.framework"); import std::os::macos::cf; -enum StatusItemLength : (double val) @deprecated("Use NSStatusItemLength.") -{ - VARIABLE { -1.0 }, - SQUARE { -2.0 }, -} - -enum ApplicationActivationPolicy : (int val) @deprecated("Use NSApplicationActivationPolicy.") -{ - REGULAR { 0 }, - ACCESSORY { 1 }, - PROHIBITED { 2 }, -} constdef NSApplicationActivationPolicy : inline NSInteger { @@ -95,29 +83,6 @@ constdef NSApplicationActivationPolicy : inline NSInteger PROHIBITED = 2, } -enum WindowStyleMask : (int val) @deprecated("Use NSWindowStyleMask.") -{ - BORDERLESS { 0 }, - TITLED { 1 << 0 }, - CLOSABLE { 1 << 1 }, - MINIATURIZABLE { 1 << 2 }, - RESIZABLE { 1 << 3 }, - TEXTURED_BACKGROUND { 1 << 8 }, - UNIFIED_TITLE_AND_TOOLBAR { 1 << 12 }, - FULL_SCREEN { 1 << 14 }, - FULL_SIZE_CONTENT_VIEW { 1 << 15 }, - UTILITY_WINDOW { 1 << 4 }, - DOC_MODAL_WINDOW { 1 << 6 }, - NONACTIVATING_PANEL { 1 << 7 }, - HUD_WINDOW { 1 << 13 } -} - -enum BackingStore : (int val) @deprecated("Use NSBackingStoreType.") -{ - RETAINED { 0 }, - NONRETAINED { 1 }, - BUFFERED { 2 } -} constdef NSBackingStoreType : inline NSUInteger { @@ -126,44 +91,6 @@ constdef NSBackingStoreType : inline NSUInteger BUFFERED = 2 } -enum EventType : (long val) @deprecated("Use NSEventType.") -{ - LEFT_MOUSE_DOWN { 1 }, - LEFT_MOUSE_UP { 2 }, - RIGHT_MOUSE_DOWN { 3 }, - RIGHT_MOUSE_UP { 4 }, - MOUSE_MOVED { 5 }, - LEFT_MOUSE_DRAGGED { 6 }, - RIGHT_MOUSE_DRAGGED { 7 }, - MOUSE_ENTERED { 8 }, - MOUSE_EXITED { 9 }, - KEY_DOWN { 10 }, - KEY_UP { 11 }, - FLAGS_CHANGED { 12 }, - APPKIT_DEFINED { 13 }, - SYSTEM_DEFINED { 14 }, - APPLICATION_DEFINED { 15 }, - PERIODIC { 16 }, - CURSOR_UPDATE { 17 }, - SCROLL_WHEEL { 22 }, - TABLET_POINT { 23 }, - TABLET_PROXIMITY { 24 }, - OTHER_MOUSE_DOWN { 25 }, - OTHER_MOUSE_UP { 26 }, - OTHER_MOUSE_DRAGGED { 27 }, - GESTURE { 29 }, - MAGNIFY { 30 }, - SWIPE { 31 }, - ROTATE { 18 }, - BEGIN_GESTURE { 19 }, - END_GESTURE { 20 }, - SMART_MAGNIFY { 32 }, - QUICK_LOOK { 33 }, - PRESSURE { 34 }, - DIRECT_TOUCH { 37 }, - CHANGE_MODE { 38 }, -} - constdef NSEventType : inline NSUInteger { LEFT_MOUSE_DOWN = 1, @@ -202,83 +129,6 @@ constdef NSEventType : inline NSUInteger CHANGE_MODE = 38, } -fn EventType? event_type_from(int val) @deprecated("Use NSEventType directly.") -{ - switch(val) - { - case EventType.LEFT_MOUSE_DOWN.val: return LEFT_MOUSE_DOWN; - case EventType.LEFT_MOUSE_UP.val: return LEFT_MOUSE_UP; - case EventType.RIGHT_MOUSE_DOWN.val: return RIGHT_MOUSE_DOWN; - case EventType.RIGHT_MOUSE_UP.val: return RIGHT_MOUSE_UP; - case EventType.MOUSE_MOVED.val: return MOUSE_MOVED; - case EventType.LEFT_MOUSE_DRAGGED.val: return LEFT_MOUSE_DRAGGED; - case EventType.RIGHT_MOUSE_DRAGGED.val: return RIGHT_MOUSE_DRAGGED; - case EventType.MOUSE_ENTERED.val: return MOUSE_ENTERED; - case EventType.MOUSE_EXITED.val: return MOUSE_EXITED; - case EventType.KEY_DOWN.val: return KEY_DOWN; - case EventType.KEY_UP.val: return KEY_UP; - case EventType.FLAGS_CHANGED.val: return FLAGS_CHANGED; - case EventType.APPKIT_DEFINED.val: return APPKIT_DEFINED; - case EventType.SYSTEM_DEFINED.val: return SYSTEM_DEFINED; - case EventType.APPLICATION_DEFINED.val: return APPLICATION_DEFINED; - case EventType.PERIODIC.val: return PERIODIC; - case EventType.CURSOR_UPDATE.val: return CURSOR_UPDATE; - case EventType.SCROLL_WHEEL.val: return SCROLL_WHEEL; - case EventType.TABLET_POINT.val: return TABLET_POINT; - case EventType.TABLET_PROXIMITY.val: return TABLET_PROXIMITY; - case EventType.OTHER_MOUSE_DOWN.val: return OTHER_MOUSE_DOWN; - case EventType.OTHER_MOUSE_UP.val: return OTHER_MOUSE_UP; - case EventType.OTHER_MOUSE_DRAGGED.val: return OTHER_MOUSE_DRAGGED; - case EventType.GESTURE.val: return GESTURE; - case EventType.MAGNIFY.val: return MAGNIFY; - case EventType.SWIPE.val: return SWIPE; - case EventType.ROTATE.val: return ROTATE; - case EventType.BEGIN_GESTURE.val: return BEGIN_GESTURE; - case EventType.END_GESTURE.val: return END_GESTURE; - case EventType.SMART_MAGNIFY.val: return SMART_MAGNIFY; - case EventType.QUICK_LOOK.val: return QUICK_LOOK; - case EventType.PRESSURE.val: return PRESSURE; - case EventType.DIRECT_TOUCH.val: return DIRECT_TOUCH; - case EventType.CHANGE_MODE.val: return CHANGE_MODE; - default: return objc::UNKNOWN_EVENT~; - } -} - -enum EventMask : (long val) @deprecated("Use NSEventMask.") -{ - LEFT_MOUSE_DOWN { 1 << EventType.LEFT_MOUSE_DOWN.val }, - LEFT_MOUSE_UP { 1 << EventType.LEFT_MOUSE_UP.val }, - RIGHT_MOUSE_DOWN { 1 << EventType.RIGHT_MOUSE_DOWN.val }, - RIGHT_MOUSE_UP { 1 << EventType.RIGHT_MOUSE_UP.val }, - MOUSE_MOVED { 1 << EventType.MOUSE_MOVED.val }, - LEFT_MOUSE_DRAGGED { 1 << EventType.LEFT_MOUSE_DRAGGED.val }, - RIGHT_MOUSE_DRAGGED { 1 << EventType.RIGHT_MOUSE_DRAGGED.val }, - MOUSE_ENTERED { 1 << EventType.MOUSE_ENTERED.val }, - MOUSE_EXITED { 1 << EventType.MOUSE_EXITED.val }, - KEY_DOWN { 1 << EventType.KEY_DOWN.val }, - KEY_UP { 1 << EventType.KEY_UP.val }, - FLAGS_CHANGED { 1 << EventType.FLAGS_CHANGED.val }, - APPKIT_DEFINED { 1 << EventType.APPKIT_DEFINED.val }, - SYSTEM_DEFINED { 1 << EventType.SYSTEM_DEFINED.val }, - APPLICATION_DEFINED { 1 << EventType.APPLICATION_DEFINED.val }, - PERIODIC { 1 << EventType.PERIODIC.val }, - CURSOR_UPDATE { 1 << EventType.CURSOR_UPDATE.val }, - SCROLL_WHEEL { 1 << EventType.SCROLL_WHEEL.val }, - TABLET_POINT { 1 << EventType.TABLET_POINT.val }, - TABLET_PROXIMITY { 1 << EventType.TABLET_PROXIMITY.val }, - OTHER_MOUSE_DOWN { 1 << EventType.OTHER_MOUSE_DOWN.val }, - OTHER_MOUSE_UP { 1 << EventType.OTHER_MOUSE_UP.val }, - OTHER_MOUSE_DRAGGED { 1 << EventType.OTHER_MOUSE_DRAGGED.val }, - GESTURE { 1 << EventType.GESTURE.val }, - MAGNIFY { 1 << EventType.MAGNIFY.val }, - SWIPE { 1 << EventType.SWIPE.val }, - ROTATE { 1 << EventType.ROTATE.val }, - BEGIN_GESTURE { 1 << EventType.BEGIN_GESTURE.val }, - END_GESTURE { 1 << EventType.END_GESTURE.val }, - SMART_MAGNIFY { 1L << EventType.SMART_MAGNIFY.val }, - DIRECT_TOUCH { 1L << EventType.DIRECT_TOUCH.val }, - ANY { long.max }, -} constdef NSEventMask : inline ulong { @@ -316,22 +166,11 @@ constdef NSEventMask : inline ulong PRESSURE = 1ul << NSEventType.PRESSURE, DIRECT_TOUCH = 1ul << NSEventType.DIRECT_TOUCH, CHANGE_MODE = 1ul << NSEventType.CHANGE_MODE, - ANY = ulong.max, + ANY = ulong::max, } fn NSEventMask event_mask_from_type(NSEventType type) => (NSEventMask)1ul << type; -enum EventModifierFlag : (int val) @deprecated("Use NSEventModifierFlags.") -{ - CAPS_LOCK { 1 << 16 }, - SHIFT { 1 << 17 }, - CONTROL { 1 << 18 }, - OPTION { 1 << 19 }, - COMMAND { 1 << 20 }, - NUMERIC_PAD { 1 << 21 }, - FUNCTION { 1 << 23 }, - HELP { 1 << 22 }, -} constdef NSEventModifierFlags : inline NSUInteger { diff --git a/test/stdlib/src/os/openbsd/general.c3 b/test/stdlib/src/os/openbsd/general.c3 index 528849a..197470f 100644 --- a/test/stdlib/src/os/openbsd/general.c3 +++ b/test/stdlib/src/os/openbsd/general.c3 @@ -60,7 +60,7 @@ fn Backtrace? backtrace_line_parse(Allocator allocator, String obj, String addr2 String source = ""; if (!parts[1].contains("?") && parts[1].contains(":")) { - usz index = parts[1].rindex_of_char(':')!; + sz index = parts[1].rindex_of_char(':')!; source = parts[1][:index]; line = parts[1][index + 1..].to_uint()!; } @@ -83,7 +83,7 @@ fn void? backtrace_add_addr2line(Allocator allocator, BacktraceList* list, Strin fn void? backtrace_add_from_exec(Allocator allocator, BacktraceList* list, String fun, String obj) @local { char[1024] buf @noinit; - String addr2line = process::execute_stdout_to_buffer(&buf, {"llvm-addr2line", "-fpe", obj, fun})!; + String addr2line = process::run_capture_stdout(&buf, {"llvm-addr2line", "-fpe", obj, fun})!; return backtrace_add_addr2line(allocator, list, obj, addr2line); } diff --git a/test/stdlib/src/os/posix/mman.c3 b/test/stdlib/src/os/posix/mman.c3 index 4adfa00..da6ea60 100644 --- a/test/stdlib/src/os/posix/mman.c3 +++ b/test/stdlib/src/os/posix/mman.c3 @@ -7,12 +7,12 @@ const PROT_READ = 0x01; // pages can be read const PROT_WRITE = 0x02; // pages can be written const PROT_EXEC = 0x04; // pages can be executed -const MAP_SHARED = 0x0001; // share changes -const MAP_PRIVATE = 0x0002; // changes are private +const CInt MAP_SHARED = 0x0001; // share changes +const CInt MAP_PRIVATE = 0x0002; // changes are private -const MAP_FILE = 0x0000; // map from file (default) +const CInt MAP_FILE = 0x0000; // map from file (default) -const MAP_ANONYMOUS = env::LINUX || env::ANDROID ??? 0x20 : 0x1000; // allocated from memory, swap space +const CInt MAP_ANONYMOUS = env::LINUX || env::ANDROID ??? 0x20 : 0x1000; // allocated from memory, swap space const void* MAP_FAILED = (void *)(uptr)-1; // mmap failed const MADV_NORMAL = 0; // no further special treatment diff --git a/test/stdlib/src/os/posix/threads.c3 b/test/stdlib/src/os/posix/threads.c3 index b985f4d..4a59c64 100644 --- a/test/stdlib/src/os/posix/threads.c3 +++ b/test/stdlib/src/os/posix/threads.c3 @@ -21,6 +21,8 @@ extern fn Pthread_t pthread_self(); extern fn Errno pthread_setcancelstate(CInt state, CInt* oldstate); extern fn Errno pthread_setcanceltype(CInt type, CInt* oldtype); extern fn Errno pthread_testcancel(); +extern fn Errno pthread_setschedparam(Pthread_t thread, CInt policy, Pthread_sched_param* param); +extern fn Errno pthread_getschedparam(Pthread_t thread, CInt* policy, Pthread_sched_param* param); extern fn Errno pthread_attr_destroy(Pthread_attr_t*); extern fn Errno pthread_attr_getinheritsched(Pthread_attr_t*, CInt*); @@ -88,6 +90,15 @@ extern fn void pthread_cleanup_push(PosixThreadFn routine, void* routine_arg); extern fn int sched_yield(); +struct Pthread_sched_param { int sched_priority; char[4] __opaque @if(env::DARWIN); } + +module std::thread::os @if(env::LINUX || env::ANDROID); + +extern fn CInt sem_init(Sem_t* sem, CInt pshared, CUInt value); +extern fn CInt sem_wait(Sem_t* sem); +extern fn CInt sem_post(Sem_t* sem); +extern fn CInt sem_destroy(Sem_t* sem); + module std::thread::os @if(env::POSIX && !env::LINUX && !env::NETBSD); typedef Pthread_attr_t = ulong[8]; typedef Pthread_cond_t = ulong[6]; @@ -98,7 +109,11 @@ typedef Pthread_mutexattr_t = ulong[2]; typedef Pthread_once_t = ulong[2]; typedef Pthread_rwlock_t = ulong[25]; typedef Pthread_rwlockattr_t = ulong[3]; -typedef Pthread_sched_param = ulong; + +union Sem_t @if (env::ANDROID) +{ + CUInt[4] __private; +} module std::thread::os @if(env::LINUX); typedef Pthread_attr_t = ulong[7]; // 24 on 32bit @@ -110,7 +125,12 @@ typedef Pthread_mutexattr_t = uint; typedef Pthread_once_t = int; typedef Pthread_rwlock_t = ulong[7]; // 32 on 3bit typedef Pthread_rwlockattr_t = uint; -typedef Pthread_sched_param = uint; + +union Sem_t @if (env::LINUX) +{ + char[32] __size; + CLong __align; +} module std::thread::os @if(env::NETBSD); typedef Pthread_attr_t = ulong[2]; @@ -120,7 +140,6 @@ typedef Pthread_key_t = uint; typedef Pthread_mutexattr_t = ulong[2]; typedef Pthread_rwlock_t = ulong[8]; typedef Pthread_rwlockattr_t = ulong[2]; -typedef Pthread_sched_param = int; typedef Pthread_mutex_t @if(env::X86_64)= ulong[6]; typedef Pthread_once_t @if(env::X86_64) = ulong[7]; diff --git a/test/stdlib/src/os/subprocess.c3 b/test/stdlib/src/os/process.c3 similarity index 76% rename from test/stdlib/src/os/subprocess.c3 rename to test/stdlib/src/os/process.c3 index f867b54..19d7c76 100644 --- a/test/stdlib/src/os/subprocess.c3 +++ b/test/stdlib/src/os/process.c3 @@ -14,7 +14,7 @@ faultdef PROCESS_TERMINATION_FAILED, READ_FAILED; -struct SubProcess +struct Process { CFile stdin_file; CFile stdout_file; @@ -31,102 +31,49 @@ struct SubProcess bool is_alive; } -bitstruct SubProcessOptions : int +constdef SpawnOptions { - <* Combine stdout and stderr to the same file *> - bool combined_stdout_stderr; - <* Child process should inherit env variables of parent process *> - bool inherit_environment; - <* Enable async reading of stdout/stderr before completion *> - bool read_async; - <* Spawn child process without window if supported *> - bool no_window; - <* - Search for program names in the PATH variable. Always enabled on Windows. - Note: this will **not** search for paths in any provided custom environment - and instead uses the PATH of the spawning process. - *> - bool search_user_path; - <* Inherit the parent's stdin, stdout, and stderr handles instead of creating pipes *> - bool inherit_stdio; + STDERR_TO_STDOUT = 1 << 0, + INHERIT_ENV = 1 << 1, + ASYNC_OUTPUT = 1 << 2, + NO_WINDOW = 1 << 3, + NO_PATH_SEARCH = 1 << 4, + INHERIT_STDIO = 1 << 5 } -fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32) +fn String? run_capture_stdout(char[] buffer, String[] command_line, SpawnOptions options = {}, String[] environment = {}) { - Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 }; - - tlocal long index = 0; - long unique = index++; - String s = string::bformat(&&(char[128]){}, `\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique); - Win32_LPCSTR str = (Win32_LPCSTR)s.ptr; - *rd = win32::createNamedPipeA( - str, - win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED, - win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT, - 1, 4096, 4096, 0, &sa_attr); - if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE~; - *wr = win32::createFileA( - str, win32::GENERIC_WRITE, 0, &sa_attr, - win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null); - if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE~; + Process process = process::spawn(command_line, options, environment)!; + defer (void)process.destroy(); + process.join()!; + sz len = process.read_stdout(buffer.ptr, buffer.len)!; + if (len == 0) return ""; + return (String)buffer[:len - 1]; } -fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local +fn int? run(String[] command_line, SpawnOptions options = {}, String[] environment = {}) { - DString str = dstring::temp_with_capacity(512); - foreach LINE: (i, s : command_line) - { - if (i != 0) str.append(' '); - - do CHECK_WS: - { - foreach (c : s) - { - switch (c) - { - case '\t': - case ' ': - case '\v': - break CHECK_WS; - } - } - str.append(s); - continue LINE; - }; - str.append('"'); - foreach (j, c : s) - { - switch (c) - { - case '\\': - if (j != s.len - 1 && s[j + 1] == '"') str.append('\\'); - case '"': - str.append('\\'); - } - str.append(c); - } - str.append('"'); - } - str.append('\0'); - return str.str_view().to_temp_wstring()!!; + Process process = process::spawn(command_line, options, environment)!; + defer (void)process.destroy(); + return process.join()!; } <* - @require !environment || !options.inherit_environment + @require !environment || !(options & INHERIT_ENV) *> -fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32) +fn Process? spawn(String[] command_line, SpawnOptions options = {}, String[] environment = {}) @if(env::WIN32) { Win32_DWORD flags = win32::CREATE_UNICODE_ENVIRONMENT; Win32_PROCESS_INFORMATION process_info; - Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 }; + Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES::size, null, 1 }; Win32_STARTUPINFOW start_info = { - .cb = Win32_STARTUPINFOW.sizeof, + .cb = Win32_STARTUPINFOW::size, }; - if (options.no_window) flags |= win32::CREATE_NO_WINDOW; + if (options & NO_WINDOW) flags |= win32::CREATE_NO_WINDOW; // Only set STARTF_USESTDHANDLES if we're not inheriting stdio - if (!options.inherit_stdio) start_info.dwFlags = win32::STARTF_USESTDHANDLES; + if (!(options & INHERIT_STDIO)) start_info.dwFlags = win32::STARTF_USESTDHANDLES; CFile stdin; CFile stdout; @@ -135,7 +82,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str void* wr = null; // Only create pipes if not inheriting stdio - if (!options.inherit_stdio) + if (!(options & INHERIT_STDIO)) { if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return FAILED_TO_CREATE_PIPE~; // TODO defer catch @@ -145,7 +92,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str @stack_mem(2048; Allocator mem) { WString used_environment = null; - if (!options.inherit_environment) + if (!(options & INHERIT_ENV)) { DString env = dstring::new_with_capacity(mem, 64); if (!environment.len) @@ -162,7 +109,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str } // Handle stdin pipe if not inheriting - if (!options.inherit_stdio) + if (!(options & INHERIT_STDIO)) { int fd = win32::_open_osfhandle((iptr)wr, 0); if (fd != -1) @@ -173,7 +120,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str start_info.hStdInput = rd; // Handle stdout pipe - if (options.read_async) + if (options & ASYNC_OUTPUT) { create_named_pipe_helper(&rd, &wr)!; } @@ -195,13 +142,13 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str // Handle stderr pipe or combine with stdout do { - if (options.combined_stdout_stderr) + if (options & STDERR_TO_STDOUT) { stderr = stdout; start_info.hStdError = start_info.hStdOutput; break; } - if (options.read_async) + if (options & ASYNC_OUTPUT) { create_named_pipe_helper(&rd, &wr)!; } @@ -223,7 +170,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str void *event_output = null; void *event_error = null; - if (!options.inherit_stdio && options.read_async) + if (!(options & INHERIT_STDIO) && (options & ASYNC_OUTPUT)) { event_output = win32::createEventA(&sa_attr, 1, 1, null); event_error = win32::createEventA(&sa_attr, 1, 1, null); @@ -246,7 +193,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str win32::closeHandle(process_info.hThread); // Close handles if not inheriting stdio - if (!options.inherit_stdio && start_info.hStdOutput) + if (!(options & INHERIT_STDIO) && start_info.hStdOutput) { win32::closeHandle(start_info.hStdOutput); if (start_info.hStdOutput != start_info.hStdError) win32::closeHandle(start_info.hStdError); @@ -254,7 +201,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str return { .hProcess = process_info.hProcess, - .hStdInput = options.inherit_stdio ? null : start_info.hStdInput, + .hStdInput = (options & INHERIT_STDIO) ? null : start_info.hStdInput, .stdin_file = stdin, .stdout_file = stdout, .stderr_file = stderr, @@ -262,46 +209,13 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str }; } -<* - @require command_line.len > 0 -*> -fn ZString* copy_command_line(Allocator mem, String[] command_line) @local @inline @if(env::POSIX) -{ - ZString* copy = allocator::alloc_array(mem, ZString, command_line.len + 1); - foreach (i, str : command_line) - { - copy[i] = str.zstr_copy(mem); - } - copy[command_line.len] = null; - return copy; -} -const ZString[1] EMPTY_ENVIRONMENT @if(env::POSIX) = { null }; -fn ZString* copy_env(Allocator mem, String[] environment) @local @inline @if(env::POSIX) -{ - if (!environment.len) return &EMPTY_ENVIRONMENT; - ZString* copy = allocator::alloc_array(mem, ZString, environment.len + 1); - copy[environment.len] = null; - foreach (i, str : environment) - { - copy[i] = str.zstr_copy(mem); - } - return copy; -} -fn String? execute_stdout_to_buffer(char[] buffer, String[] command_line, SubProcessOptions options = {}, String[] environment = {}) -{ - SubProcess process = process::create(command_line, options, environment)!; - process.join()!; - usz len = process.read_stdout(buffer.ptr, buffer.len)!; - if (len == 0) return ""; - return (String)buffer[:len - 1]; -} <* - @require !environment || !options.inherit_environment + @require !environment || !(options & INHERIT_ENV) *> -fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::POSIX) +fn Process? spawn(String[] command_line, SpawnOptions options = { }, String[] environment = {}) @if(env::POSIX) { CInt[2] stdinfd; CInt[2] stdoutfd; @@ -315,17 +229,31 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str defer posix::spawn_file_actions_destroy(&actions); // Only set up pipes if not inheriting stdio - if (!options.inherit_stdio) + if (!(options & INHERIT_STDIO)) { if (posix::pipe(&stdinfd)) return FAILED_TO_OPEN_STDIN~; if (posix::pipe(&stdoutfd)) return FAILED_TO_OPEN_STDOUT~; - if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR~; + if (!(options & STDERR_TO_STDOUT) && posix::pipe(&stderrfd)) return FAILED_TO_OPEN_STDERR~; + + // We should set FD_CLOEXEC to prevent leaking pipe handles between concurrent spawns + const int F_GETFD = 1; + const int F_SETFD = 2; + const int FD_CLOEXEC = 1; + os::fcntl((NativeSocket)stdinfd[0], F_SETFD, FD_CLOEXEC); + os::fcntl((NativeSocket)stdinfd[1], F_SETFD, FD_CLOEXEC); + os::fcntl((NativeSocket)stdoutfd[0], F_SETFD, FD_CLOEXEC); + os::fcntl((NativeSocket)stdoutfd[1], F_SETFD, FD_CLOEXEC); + if (!(options & STDERR_TO_STDOUT)) + { + os::fcntl((NativeSocket)stderrfd[0], F_SETFD, FD_CLOEXEC); + os::fcntl((NativeSocket)stderrfd[1], F_SETFD, FD_CLOEXEC); + } - if (options.read_async) + if (options & ASYNC_OUTPUT) { ((NativeSocket)stdoutfd[0]).set_non_blocking(true)!; ((NativeSocket)stdoutfd[1]).set_non_blocking(true)!; - if (!options.combined_stdout_stderr) + if (!(options & STDERR_TO_STDOUT)) { ((NativeSocket)stderrfd[0]).set_non_blocking(true)!; ((NativeSocket)stderrfd[1]).set_non_blocking(true)!; @@ -340,7 +268,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return FAILED_TO_OPEN_STDOUT~; if (posix::spawn_file_actions_addclose(&actions, stdoutfd[1])) return FAILED_TO_OPEN_STDERR~; - if (options.combined_stdout_stderr) + if (options & STDERR_TO_STDOUT) { if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return FAILED_TO_OPEN_STDERR~; } @@ -356,19 +284,19 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str @stack_mem(2048; Allocator mem) { ZString* command_line_copy = copy_command_line(mem, command_line); - ZString* used_environment = options.inherit_environment ? posix::environ : copy_env(mem, environment); - if (options.search_user_path) + ZString* used_environment = options & INHERIT_ENV ? posix::environ : copy_env(mem, environment); + if (options & NO_PATH_SEARCH) { - if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS~; + if (posix::spawn(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS~; } else { - if (posix::spawn(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS~; + if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS~; } }; // Only set up file handles if not inheriting stdio - if (!options.inherit_stdio) + if (!(options & INHERIT_STDIO)) { libc::close(stdinfd[0]); stdin = libc::fdopen(stdinfd[1], "wb"); @@ -377,7 +305,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str do { - if (options.combined_stdout_stderr) + if (options & STDERR_TO_STDOUT) { stderr = stdout; break; @@ -396,7 +324,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str }; } -fn CInt? SubProcess.join(&self) @if(env::POSIX) +fn CInt? Process.join(&self) @if(env::POSIX) { if (self.stdin_file) { @@ -412,19 +340,19 @@ fn CInt? SubProcess.join(&self) @if(env::POSIX) return self.return_status = posix::wIFEXITED(status) ? posix::wEXITSTATUS(status) : libc::EXIT_FAILURE; } -fn File SubProcess.stdout(&self) +fn File Process.stdout(&self) { if (!self.stdout_file) return (File){}; // Return an empty File struct return file::from_handle(self.stdout_file); } -fn File SubProcess.stderr(&self) +fn File Process.stderr(&self) { if (!self.stderr_file) return (File){}; // Return an empty File struct return file::from_handle(self.stderr_file); } -fn CInt? SubProcess.join(&self) @if(env::WIN32) +fn CInt? Process.join(&self) @if(env::WIN32) { if (self.stdin_file) { @@ -440,10 +368,10 @@ fn CInt? SubProcess.join(&self) @if(env::WIN32) Win32_DWORD return_code @noinit; if (!win32::getExitCodeProcess(self.hProcess, &return_code)) return PROCESS_JOIN_FAILED~; self.is_alive = false; - return return_code; + return (CInt)return_code; } -fn bool SubProcess.destroy(&self) +fn bool Process.destroy(&self) { if (self.stdin_file) libc::fclose(self.stdin_file); if (self.stdout_file) @@ -462,7 +390,7 @@ fn bool SubProcess.destroy(&self) return true; } -fn void? SubProcess.terminate(&self) +fn void? Process.terminate(&self) { $if env::WIN32: if (!win32::terminateProcess(self.hProcess, 99)) return PROCESS_TERMINATION_FAILED~; @@ -472,9 +400,9 @@ fn void? SubProcess.terminate(&self) } <* - @require size <= Win32_DWORD.max + @require size <= Win32_DWORD::max *> -fn usz? read_from_file_win32(CFile file, Win32_HANDLE event_handle, char* buffer, usz size) @if(env::WIN32) @local +fn sz? read_from_file_win32(CFile file, Win32_HANDLE event_handle, char* buffer, sz size) @if(env::WIN32) @local { CInt fd = libc::fileno(file); Win32_DWORD bytes_read = 0; @@ -501,18 +429,18 @@ fn usz? read_from_file_win32(CFile file, Win32_HANDLE event_handle, char* buffer return bytes_read; } -fn usz? read_from_file_posix(CFile file, char* buffer, usz size) @if(env::POSIX) @local +fn sz? read_from_file_posix(CFile file, char* buffer, sz size) @if(env::POSIX) @local { - isz bytes_read = libc::read(libc::fileno(file), buffer, size); + sz bytes_read = libc::read(libc::fileno(file), buffer, (usz)size); if (bytes_read < 0) { - if (libc::errno() == errno::EAGAIN) return 0; // nothing to read but O_NONBLOCK (async) is used + if (libc::errno() == EAGAIN) return 0; // nothing to read but O_NONBLOCK (async) is used return READ_FAILED~; } return bytes_read; } -fn usz? SubProcess.read_stdout(&self, char* buffer, usz size) +fn sz? Process.read_stdout(&self, char* buffer, sz size) { if (!self.stdout_file) return 0; // No output available when inheriting stdio @@ -524,7 +452,7 @@ fn usz? SubProcess.read_stdout(&self, char* buffer, usz size) } -fn usz? SubProcess.read_stderr(&self, char* buffer, usz size) +fn sz? Process.read_stderr(&self, char* buffer, sz size) { if (!self.stderr_file) return 0; // No output available when inheriting stdio @@ -535,7 +463,7 @@ fn usz? SubProcess.read_stderr(&self, char* buffer, usz size) $endif } -fn bool? SubProcess.is_running(&self) +fn bool? Process.is_running(&self) { if (!self.is_alive) return false; $if env::WIN32: @@ -554,9 +482,95 @@ fn bool? SubProcess.is_running(&self) $endif } -fn usz? SubProcess.write_to_stdin(&self, char[] buffer) +fn sz? Process.write_stdin(&self, char[] buffer) { if (!self.stdin_file) return FAILED_TO_OPEN_STDIN~; defer try (void)os::native_fflush(self.stdin_file); return os::native_fwrite(self.stdin_file, buffer); } +fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32) +{ + Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES::size, null, 1 }; + + tlocal long index = 0; + long unique = index++; + String s = string::bformat(&&(char[128]){}, `\\.\pipe\c3_process.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique); + Win32_LPCSTR str = (Win32_LPCSTR)s.ptr; + *rd = win32::createNamedPipeA( + str, + win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED, + win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT, + 1, 4096, 4096, 0, &sa_attr); + if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE~; + *wr = win32::createFileA( + str, win32::GENERIC_WRITE, 0, &sa_attr, + win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null); + if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE~; +} + +fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local +{ + DString str = dstring::temp_with_capacity(512); + foreach LINE: (i, s : command_line) + { + if (i != 0) str.append(' '); + + do CHECK_WS: + { + foreach (c : s) + { + switch (c) + { + case '\t': + case ' ': + case '\v': + break CHECK_WS; + } + } + str.append(s); + continue LINE; + }; + str.append('"'); + foreach (j, c : s) + { + switch (c) + { + case '\\': + if (j != s.len - 1 && s[j + 1] == '"') str.append('\\'); + case '"': + str.append('\\'); + } + str.append(c); + } + str.append('"'); + } + str.append('\0'); + return str.str_view().to_temp_wstring()!!; +} + +<* + @require command_line.len > 0 +*> +fn ZString* copy_command_line(Allocator mem, String[] command_line) @local @inline @if(env::POSIX) +{ + ZString* copy = alloc::alloc_array(mem, ZString, command_line.len + 1); + foreach (i, str : command_line) + { + copy[i] = str.zstr_copy(mem); + } + copy[command_line.len] = null; + return copy; +} + +const ZString[1] EMPTY_ENVIRONMENT @if(env::POSIX) = { null }; +fn ZString* copy_env(Allocator mem, String[] environment) @local @inline @if(env::POSIX) +{ + if (!environment.len) return &EMPTY_ENVIRONMENT; + ZString* copy = alloc::alloc_array(mem, ZString, environment.len + 1); + copy[environment.len] = null; + foreach (i, str : environment) + { + copy[i] = str.zstr_copy(mem); + } + return copy; +} diff --git a/test/stdlib/src/os/win32/exception.c3 b/test/stdlib/src/os/win32/exception.c3 index 46b435f..3101952 100644 --- a/test/stdlib/src/os/win32/exception.c3 +++ b/test/stdlib/src/os/win32/exception.c3 @@ -123,9 +123,9 @@ struct ExceptionPointers alias UnhandledExceptionFilter = fn Win32_LONG (ExceptionPointers* exception_info); const EXCEPTION_EXECUTE_HANDLER = 1; -extern fn void debugBreak() @extern("DebugBreak") @if(env::WIN32); -extern fn bool isDebuggerPresent() @extern("IsDebuggerPresent") @if(env::WIN32); -extern fn UnhandledExceptionFilter setUnhandledExceptionFilter(UnhandledExceptionFilter filter) @extern("SetUnhandledExceptionFilter") @if(env::WIN32); +extern fn void debugBreak() @cname("DebugBreak") @if(env::WIN32); +extern fn bool isDebuggerPresent() @cname("IsDebuggerPresent") @if(env::WIN32); +extern fn UnhandledExceptionFilter setUnhandledExceptionFilter(UnhandledExceptionFilter filter) @cname("SetUnhandledExceptionFilter") @if(env::WIN32); UnhandledExceptionFilter previous_filter; PanicFn previous_panic; @@ -174,7 +174,7 @@ fn Win32_LONG exception_handler(ExceptionPointers* exception_info) return EXCEPTION_EXECUTE_HANDLER; } -fn void panic_tracker(String message, String file, String function, uint line) +fn void panic_tracker(String message, String file, String function, int line) { has_panicked = true; previous_panic(message, file, function, line); diff --git a/test/stdlib/src/os/win32/process.c3 b/test/stdlib/src/os/win32/process.c3 index 53deaaa..0436b1c 100644 --- a/test/stdlib/src/os/win32/process.c3 +++ b/test/stdlib/src/os/win32/process.c3 @@ -1,6 +1,16 @@ module std::os::win32 @if(env::WIN32); import std::thread, std::os::backtrace; +const THREAD_PRIORITY_ABOVE_NORMAL = 1; +const THREAD_PRIORITY_BELOW_NORMAL = -1; +const THREAD_PRIORITY_HIGHEST = 2; +const THREAD_PRIORITY_IDLE = -15; +const THREAD_PRIORITY_LOWEST = -2; +const THREAD_PRIORITY_NORMAL = 0; +const THREAD_PRIORITY_TIME_CRITICAL = 15; + +const win32::Win32_DWORD CREATE_SUSPENDED = 0x00000004; +const win32::Win32_DWORD STACK_SIZE_PARAM_IS_A_RESERVATION = 0x00010000; const Win32_DWORD STARTF_USESTDHANDLES = 0x00000100; const Win32_DWORD CREATE_NO_WINDOW = 0x08000000; const Win32_DWORD CREATE_PROTECTED_PROCESS = 0x00040000; @@ -73,6 +83,7 @@ extern fn Win32_DWORD waitForSingleObjectEx(Win32_HANDLE hHandle, Win32_DWORD dw extern fn Win32_DWORD waitForMultipleObjects(Win32_DWORD nCount, Win32_HANDLE* lpHandles, Win32_BOOL bWaitAll, Win32_DWORD dwMilliseconds) @cname("WaitForMultipleObjects"); extern fn Win32_DWORD waitForMultipleObjectsEx(Win32_DWORD nCount, Win32_HANDLE* lpHandles, Win32_BOOL bWaitAll, Win32_DWORD dwMilliseconds, Win32_BOOL bAlertable) @cname("WaitForMultipleObjectsEx"); extern fn void sleep(uint ms) @cname("Sleep"); +extern fn Win32_BOOL setThreadPriority(Win32_HANDLE hThread, CInt nPriority) @cname("SetThreadPriority"); extern fn Win32_BOOL resetEvent(Win32_HANDLE event) @cname("ResetEvent"); extern fn Win32_BOOL setEvent(Win32_HANDLE handle) @cname("SetEvent"); extern fn long interlockedCompareExchange(int* dest, int exchange, int comperand) @cname("InterlockedCompareExchange"); @@ -138,7 +149,7 @@ fn Win32_DWORD? load_modules() foreach (mod : modules) { Win32_MODULEINFO info; - if (!getModuleInformation(process, mod, &info, $sizeof(info))) + if (!getModuleInformation(process, mod, &info, @sizeof(info))) { return backtrace::RESOLUTION_FAILED~; } @@ -183,15 +194,15 @@ fn Backtrace? resolve_backtrace(Allocator allocator, void* addr, Win32_HANDLE pr { Symbol symbol; //Win32_DWORD image_type = load_modules()!; - symbol.sizeOfStruct = Win32_SYMBOL_INFO.sizeof; + symbol.sizeOfStruct = Win32_SYMBOL_INFO::size; symbol.maxNameLen = 255; - if (!symFromAddr(process, (Win32_DWORD64)addr - 1, &displacement, &symbol)) + if (!symFromAddr(process, (Win32_DWORD64)addr - 1u, &displacement, &symbol)) { return backtrace::NO_BACKTRACE_SYMBOLS~; } Win32_IMAGEHLP_MODULE64 module_info; - module_info.sizeOfStruct = Win32_IMAGEHLP_MODULE64.sizeof; - if (!symGetModuleInfo64(process, (Win32_DWORD64)addr - 1, &module_info)) + module_info.sizeOfStruct = Win32_IMAGEHLP_MODULE64::size; + if (!symGetModuleInfo64(process, (Win32_DWORD64)addr - 1u, &module_info)) { return backtrace::NO_BACKTRACE_SYMBOLS~; } @@ -202,12 +213,12 @@ fn Backtrace? resolve_backtrace(Allocator allocator, void* addr, Win32_HANDLE pr Win32_IMAGEHLP_LINE64 line; Backtrace backtrace; ZString zname = (ZString)&name; - if (!symGetLineFromAddr64(process, (Win32_ULONG64)addr - 1, &offset, &line)) + if (!symGetLineFromAddr64(process, (Win32_ULONG64)addr - 1u, &offset, &line)) { - backtrace.init(allocator, (uptr)addr, zname.str_view(), module_name.str_view()); + backtrace.init(allocator, (iptr)addr, zname.str_view(), module_name.str_view()); return backtrace; } String filename = ((ZString)line.fileName).str_view(); - backtrace.init(allocator, (uptr)addr, zname.str_view(), module_name.str_view(), file: filename, line: line.lineNumber); + backtrace.init(allocator, (iptr)addr, zname.str_view(), module_name.str_view(), file: filename, line: (int)line.lineNumber); return backtrace; } diff --git a/test/stdlib/src/os/win32/types.c3 b/test/stdlib/src/os/win32/types.c3 index 3df9626..816f7ac 100644 --- a/test/stdlib/src/os/win32/types.c3 +++ b/test/stdlib/src/os/win32/types.c3 @@ -118,7 +118,7 @@ alias Win32_POINTER_SIGNED = iptr; alias Win32_POINTER_UNSIGNED = uptr; alias Win32_PSHORT = Win32_SHORT*; alias Win32_PSIZE_T = usz*; -alias Win32_PSSIZE_T = isz*; +alias Win32_PSSIZE_T = sz*; alias Win32_PSTR = Win32_CHAR*; alias Win32_PTBYTE = Win32_TBYTE*; alias Win32_PTCHAR = Win32_TCHAR*; @@ -149,7 +149,7 @@ alias Win32_SERVICE_STATUS_HANDLE = Win32_HANDLE; alias Win32_SHORT = short; alias Win32_SIZE_T = usz; alias Win32_SOCKET = Win32_HANDLE; -alias Win32_SSIZE_T = isz; +alias Win32_SSIZE_T = sz; alias Win32_TBYTE = Win32_WCHAR; alias Win32_TCHAR = Win32_WCHAR; alias Win32_UCHAR = char; diff --git a/test/stdlib/src/os/win32/wsa.c3 b/test/stdlib/src/os/win32/wsa.c3 index 85c9812..c40d99f 100644 --- a/test/stdlib/src/os/win32/wsa.c3 +++ b/test/stdlib/src/os/win32/wsa.c3 @@ -2,8 +2,6 @@ module std::os::win32 @if(env::WIN32) @link("ws2_32"); // See https://github.com/wine-mirror/wine/blob/master/include/winsock2.h -typedef WSAError = int; - struct Win32_pollfd { Win32_SOCKET fd; @@ -134,100 +132,102 @@ const int SIOCATMARK = 1074033415; module std::os::win32::wsa @if(env::WIN32); -const WSAError NO_ERROR = 0; -const WSAError INVALID_HANDLE = 6; -const WSAError NOT_ENOUGH_MEMORY = 8; -const WSAError INVALID_PARAMETER = 87; -const WSAError OPERATION_ABORTED = 995; -const WSAError IO_INCOMPLETE = 996; -const WSAError IO_PENDING = 997; -const WSAError EINTR = 10004; -const WSAError EBADF = 10009; -const WSAError EACCESS = 10013; -const WSAError EFAULT = 10014; -const WSAError EINVAL = 10022; -const WSAError EMFILE = 10024; -const WSAError EWOULDBLOCK = 10035; -const WSAError EINPROGRESS = 10036; -const WSAError EALREADY = 10037; -const WSAError ENOTSOCK = 10038; -const WSAError EDESTADDRREQ = 10039; -const WSAError EMSGSIZE = 10040; -const WSAError EPROTOTYPE = 10041; -const WSAError ENOPROTOOPT = 10042; -const WSAError EPROTONOSUPPORT = 10043; -const WSAError ESOCKTNOSUPPORT = 10044; -const WSAError EOPNOTSUPP = 10045; -const WSAError EPFNOSUPPORT = 10046; -const WSAError EAFNOSUPPORT = 10047; -const WSAError EADDRINUSE = 10048; -const WSAError EADDRNOTAVAIL = 10049; -const WSAError ENETDOWN = 10050; -const WSAError ENETUNREACH = 10051; -const WSAError ENETRESET = 10052; -const WSAError ECONNABORTED = 10053; -const WSAError ECONNRESET = 10054; -const WSAError ENOBUFS = 10055; -const WSAError EISCONN = 10056; -const WSAError ENOTCONN = 10057; -const WSAError ESHUTDOWN = 10058; -const WSAError ETOOMANYREFS = 10059; -const WSAError ETIMEDOUT = 10060; -const WSAError ECONNREFUSED = 10061; -const WSAError ELOOP = 10062; -const WSAError ENAMETOOLONG = 10063; -const WSAError EHOSTDOWN = 10064; -const WSAError EHOSTUNREACH = 10065; -const WSAError ENOTEMPTY = 10066; -const WSAError EPROCLIM = 10067; -const WSAError EUSERS = 10068; -const WSAError EDQUOT = 10069; -const WSAError ESTALE = 10070; -const WSAError EREMOTE = 10071; -const WSAError SYSNOTREADY = 10091; -const WSAError VERNOTSUPPORTED = 10092; -const WSAError NOTINITIALISED = 10093; -const WSAError EDISCON = 10101; -const WSAError ENOMORE = 10102; -const WSAError ECANCELLED = 10103; -const WSAError EINVALIDPROCTABLE = 10104; -const WSAError EINVALIDPROVIDER = 10105; -const WSAError EPROVIDERFAILEDINIT = 10106; -const WSAError SYSCALLFAILURE = 10107; -const WSAError SERVICE_NOT_FOUND = 10108; -const WSAError TYPE_NOT_FOUND = 10109; -const WSAError E_NO_MORE = 10110; -const WSAError E_CANCELLED = 10111; -const WSAError REFUSED = 10112; -const WSAError HOST_NOT_FOUND = 11001; -const WSAError TRY_AGAIN = 11002; -const WSAError NO_RECOVERY = 11003; -const WSAError NO_DATA = 11004; -const WSAError QOS_RECEIVERS = 11005; -const WSAError QOS_SENDERS = 11006; -const WSAError QOS_NO_SENDERS = 11007; -const WSAError QOS_NO_RECEIVERS = 11008; -const WSAError QOS_REQUEST_CONFIRMED = 11009; -const WSAError QOS_ADMISSION_FAILURE = 11010; -const WSAError QOS_POLICY_FAILURE = 11011; -const WSAError QOS_BAD_STYLE = 11012; -const WSAError QOS_BAD_OBJECT = 11013; -const WSAError QOS_TRAFFIC_CTRL_ERROR = 11014; -const WSAError QOS_GENERIC_ERROR = 11015; -const WSAError QOS_ESERVICETYPE = 11016; -const WSAError QOS_EFLOWSPEC = 11017; -const WSAError QOS_EPROVSPECBUF = 11018; -const WSAError QOS_EFILTERSTYLE = 11019; -const WSAError QOS_EFILTERTYPE = 11020; -const WSAError QOS_EFILTERCOUNT = 11021; -const WSAError QOS_EOBJLENGTH = 11022; -const WSAError QOS_EFLOWCOUNT = 11023; -const WSAError QOS_EUNKOWNPSOBJ = 11024; -const WSAError QOS_EPOLICYOBJ = 11025; -const WSAError QOS_EFLOWDESC = 11026; -const WSAError QOS_EPSFLOWSPEC = 11027; -const WSAError QOS_EPSFILTERSPEC = 11028; -const WSAError QOS_ESDMODEOBJ = 11029; -const WSAError QOS_ESHAPERATEOBJ = 11030; -const WSAError QOS_RESERVED_PETYPE = 11031; - +constdef WSAError : int +{ + NO_ERROR = 0, + INVALID_HANDLE = 6, + NOT_ENOUGH_MEMORY = 8, + INVALID_PARAMETER = 87, + OPERATION_ABORTED = 995, + IO_INCOMPLETE = 996, + IO_PENDING = 997, + EINTR = 10004, + EBADF = 10009, + EACCESS = 10013, + EFAULT = 10014, + EINVAL = 10022, + EMFILE = 10024, + EWOULDBLOCK = 10035, + EINPROGRESS = 10036, + EALREADY = 10037, + ENOTSOCK = 10038, + EDESTADDRREQ = 10039, + EMSGSIZE = 10040, + EPROTOTYPE = 10041, + ENOPROTOOPT = 10042, + EPROTONOSUPPORT = 10043, + ESOCKTNOSUPPORT = 10044, + EOPNOTSUPP = 10045, + EPFNOSUPPORT = 10046, + EAFNOSUPPORT = 10047, + EADDRINUSE = 10048, + EADDRNOTAVAIL = 10049, + ENETDOWN = 10050, + ENETUNREACH = 10051, + ENETRESET = 10052, + ECONNABORTED = 10053, + ECONNRESET = 10054, + ENOBUFS = 10055, + EISCONN = 10056, + ENOTCONN = 10057, + ESHUTDOWN = 10058, + ETOOMANYREFS = 10059, + ETIMEDOUT = 10060, + ECONNREFUSED = 10061, + ELOOP = 10062, + ENAMETOOLONG = 10063, + EHOSTDOWN = 10064, + EHOSTUNREACH = 10065, + ENOTEMPTY = 10066, + EPROCLIM = 10067, + EUSERS = 10068, + EDQUOT = 10069, + ESTALE = 10070, + EREMOTE = 10071, + SYSNOTREADY = 10091, + VERNOTSUPPORTED = 10092, + NOTINITIALISED = 10093, + EDISCON = 10101, + ENOMORE = 10102, + ECANCELLED = 10103, + EINVALIDPROCTABLE = 10104, + EINVALIDPROVIDER = 10105, + EPROVIDERFAILEDINIT = 10106, + SYSCALLFAILURE = 10107, + SERVICE_NOT_FOUND = 10108, + TYPE_NOT_FOUND = 10109, + E_NO_MORE = 10110, + E_CANCELLED = 10111, + REFUSED = 10112, + HOST_NOT_FOUND = 11001, + TRY_AGAIN = 11002, + NO_RECOVERY = 11003, + NO_DATA = 11004, + QOS_RECEIVERS = 11005, + QOS_SENDERS = 11006, + QOS_NO_SENDERS = 11007, + QOS_NO_RECEIVERS = 11008, + QOS_REQUEST_CONFIRMED = 11009, + QOS_ADMISSION_FAILURE = 11010, + QOS_POLICY_FAILURE = 11011, + QOS_BAD_STYLE = 11012, + QOS_BAD_OBJECT = 11013, + QOS_TRAFFIC_CTRL_ERROR = 11014, + QOS_GENERIC_ERROR = 11015, + QOS_ESERVICETYPE = 11016, + QOS_EFLOWSPEC = 11017, + QOS_EPROVSPECBUF = 11018, + QOS_EFILTERSTYLE = 11019, + QOS_EFILTERTYPE = 11020, + QOS_EFILTERCOUNT = 11021, + QOS_EOBJLENGTH = 11022, + QOS_EFLOWCOUNT = 11023, + QOS_EUNKOWNPSOBJ = 11024, + QOS_EPOLICYOBJ = 11025, + QOS_EFLOWDESC = 11026, + QOS_EPSFLOWSPEC = 11027, + QOS_EPSFILTERSPEC = 11028, + QOS_ESDMODEOBJ = 11029, + QOS_ESHAPERATEOBJ = 11030, + QOS_RESERVED_PETYPE = 11031, +} diff --git a/test/stdlib/src/sort/binarysearch.c3 b/test/stdlib/src/sort/binarysearch.c3 index 53fd6e9..daddd94 100644 --- a/test/stdlib/src/sort/binarysearch.c3 +++ b/test/stdlib/src/sort/binarysearch.c3 @@ -9,24 +9,24 @@ in [0, array.len) where x would be inserted or cmp(i) is true and cmp(j) is true @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro usz binarysearch(list, element, cmp = ..., context = ...) @builtin +macro sz binarysearch(list, element, cmp = ..., context = ...) @builtin { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; return _binarysearch{$typeof(list), $typeof(element), $typeof(used_cmp), $typeof(used_ctx)}(list, element, used_cmp, used_ctx); } -fn usz _binarysearch(ListType list, ElementType element, CmpFnType cmp, ContextType context) @noinline @local +fn sz _binarysearch(ListType list, ElementType element, CmpFnType cmp, ContextType context) @noinline @local { - usz i; + sz i; var $no_cmp = $typeof(cmp) == TypeNotSet; var $has_context = $typeof(context) != TypeNotSet; - $if $kindof(list) == SLICE: - usz len = lengthof(list); - for (usz j = len; i < j;) + $if @kindof(list) == SLICE: + sz len = lengthof(list); + for (sz j = len; i < j;) { - usz half = i + (j - i) / 2; + sz half = i + (j - i) / 2; $if $no_cmp: switch { @@ -59,10 +59,10 @@ fn usz _binarysearch(ListType list, ElementType element, CmpFnType cmp, ContextT $endif } $else - usz len = lengthof(*list); - for (usz j = len; i < j;) + sz len = lengthof(*list); + for (sz j = len; i < j;) { - usz half = i + (j - i) / 2; + sz half = i + (j - i) / 2; $if $no_cmp: switch { @@ -73,7 +73,7 @@ fn usz _binarysearch(ListType list, ElementType element, CmpFnType cmp, ContextT $else $switch: $case $defined(cmp((*list)[0], (*list)[0], context)): - int res = cmp(list[half], element, context); + int res = cmp((*list)[half], element, context); $case $defined(cmp((*list)[0], (*list)[0])): assert(!$has_context); int res = cmp((*list)[half], element); diff --git a/test/stdlib/src/sort/countingsort.c3 b/test/stdlib/src/sort/countingsort.c3 index ea65657..f201c6f 100644 --- a/test/stdlib/src/sort/countingsort.c3 +++ b/test/stdlib/src/sort/countingsort.c3 @@ -9,8 +9,8 @@ module std::sort; *> macro void countingsort(list, key_fn = ...) @builtin { - var list_length = $kindof(list) == SLICE ??? list.len : lengthof(*list); - var $ListType = $kindof(list) == SLICE ??? $typeof(list) : $typeof(*list); + var list_length = @kindof(list) == SLICE ??? list.len : lengthof(*list); + var $ListType = @kindof(list) == SLICE ??? $typeof(list) : $typeof(*list); $if $defined(key_fn): csort{$ListType, $typeof(key_fn)}(list, 0, list_length, key_fn, ~(uint)0); $else @@ -22,10 +22,10 @@ macro void insertionsort_indexed(list, start, end, cmp = ..., context = ...) @bu { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; - $if $kindof(list) == SLICE: - isort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (usz)start, (usz)end, used_cmp, used_ctx); + $if @kindof(list) == SLICE: + isort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (sz)start, (sz)end, used_cmp, used_ctx); $else - isort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (usz)start, (usz)end, used_cmp, used_ctx); + isort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (sz)start, (sz)end, used_cmp, used_ctx); $endif } @@ -34,44 +34,44 @@ macro void quicksort_indexed(list, start, end, cmp = ..., context = ...) @builti { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; - $if $kindof(list) == SLICE: - qsort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (isz)start, (isz)(end-1), used_cmp, used_ctx); + $if @kindof(list) == SLICE: + qsort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (sz)start, (sz)(end-1), used_cmp, used_ctx); $else - qsort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (isz)start, (isz)(end-1), used_cmp, used_ctx); + qsort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (sz)start, (sz)(end-1), used_cmp, used_ctx); $endif } module std::sort @local; -alias Counts @private = usz[256]; -alias Ranges @private = usz[257]; +alias Counts @private = sz[256]; +alias Ranges @private = sz[257]; alias Indexs @private = char[256]; alias ElementType = $typeof((Type){}[0]); -const bool NO_KEY_FN @private = KeyFn.kindof != FUNC; -const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $defined($typefrom(KeyFn.paramsof[0].type) x = (ElementType){}); +const bool NO_KEY_FN @private = KeyFn::kind != FUNC; +const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $defined($typefrom(KeyFn::params[0].type) x = (ElementType){}); const bool LIST_HAS_REF @private = $defined(&(Type){}[0]); -alias KeyFnReturnType @if(!NO_KEY_FN) = $typefrom(KeyFn.returns) ; +alias KeyFnReturnType @if(!NO_KEY_FN) = $typefrom(KeyFn::returns) ; alias KeyFnReturnType @if(NO_KEY_FN) = ElementType; alias CmpCallback @if(KEY_BY_VALUE && NO_KEY_FN) = fn int(ElementType, ElementType) ; alias CmpCallback @if(!KEY_BY_VALUE && NO_KEY_FN) = fn int(ElementType*, ElementType*); alias CmpCallback @if(KEY_BY_VALUE && !NO_KEY_FN) = fn int(ElementType, ElementType, KeyFn); alias CmpCallback @if(!KEY_BY_VALUE && !NO_KEY_FN) = fn int(ElementType*, ElementType*, KeyFn); -const bool IS_SLICE = Type.kindof == SLICE; +const bool IS_SLICE = Type::kind == SLICE; alias ListType = $typefrom(IS_SLICE ??? Type : Type*); macro list_get(ListType l, i) @if(!IS_SLICE) => (*l)[i]; macro list_get(ListType l, i) @if(IS_SLICE) => l[i]; macro list_get_ref(ListType l, i) @if(!IS_SLICE) => &(*l)[i]; macro list_get_ref(ListType l, i) @if(IS_SLICE) => &l[i]; -fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) @if (!NO_KEY_FN) @private +fn void csort(ListType list, sz low, sz high, KeyFn key_fn, sz byte_idx) @if (!NO_KEY_FN) @private { _csort(list, low, high, byte_idx, key_fn); } -fn void csort(ListType list, usz low, usz high, uint byte_idx) @if (NO_KEY_FN) @private +fn void csort(ListType list, sz low, sz high, sz byte_idx) @if (NO_KEY_FN) @private { _csort(list, low, high, byte_idx); } @@ -85,7 +85,7 @@ fn void csort(ListType list, usz low, usz high, uint byte_idx) @if (NO_KEY_FN) @ In other words, `csort(a_list, low, high, key_fn: some_fn, byte_idx: index_val)` breaks the existing API since explicitly naming `byte_idx` becomes a requirement. *> -macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn = ...) +macro void _csort(ListType list, sz low, sz high, sz byte_idx, KeyFn key_fn = ...) { if (high <= low) return; $if NO_KEY_FN: @@ -94,7 +94,7 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn CmpCallback compare_fn = fn (lhs, rhs, key_fn) => compare_to(key_fn(lhs), key_fn(rhs)); $endif; - byte_idx = byte_idx >= KeyFnReturnType.sizeof ? KeyFnReturnType.sizeof - 1 : byte_idx; + byte_idx = byte_idx >= KeyFnReturnType::size ? KeyFnReturnType::size - 1 : byte_idx; Counts counts; Ranges ranges; @@ -106,7 +106,7 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn char last_key = 0; char keys_ordered = 1; - for (usz i = low; i < high; i++) + for (sz i = low; i < high; i++) { $switch: $case NO_KEY_FN: @@ -136,7 +136,7 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn ushort fallback1_count = 0; ushort recursion_count = 0; - usz total = 0; + sz total = 0; foreach (char i, count : counts) { indexs[fallback0_count] = i; @@ -151,10 +151,11 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn } ranges[256] = total; - ushort remaining_indexs = 256 - (fallback0_count + recursion_count); - for(ushort i = 0; (i < 256) && remaining_indexs; i++) { + ushort remaining_indexs = 256u - (fallback0_count + recursion_count); + for (ushort i = 0; (i < 256) && remaining_indexs; i++) + { indexs[fallback0_count + fallback1_count] = (char)i; - usz count = ranges[i + 1] - ranges[i]; + sz count = ranges[i + 1] - ranges[i]; ushort within_fallback1_range = (ushort)(count > 32 && count <= 128); fallback1_count += within_fallback1_range; remaining_indexs -= within_fallback1_range; @@ -162,13 +163,13 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn if (!keys_ordered) { - usz sorted_count = 0; + sz sorted_count = 0; do { foreach (x, s : counts) { - usz e = ranges[x + 1]; + sz e = ranges[x + 1]; sorted_count += (e - s); for (; s < e; s++) { @@ -183,7 +184,7 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn KeyFnReturnType k = key_fn(&&list_get(list, low + s)); $endswitch; char k_idx = (char)(k >> (byte_idx * 8)); - usz target_idx = counts[k_idx]; + sz target_idx = counts[k_idx]; $if IS_SLICE: @swap(list[low + s], list[low + target_idx]); $else @@ -197,35 +198,35 @@ macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn if (byte_idx) { - for (usz p = 0; p < fallback0_count; p++) { - usz i = indexs[p]; + for (sz p = 0; p < fallback0_count; p++) { + sz i = indexs[p]; - usz start_offset = ranges[i]; - usz end_offset = ranges[i + 1]; + sz start_offset = ranges[i]; + sz end_offset = ranges[i + 1]; insertionsort_indexed(list, low + start_offset, low + end_offset, compare_fn, ...key_fn); } - for (usz p = 0; p < fallback1_count; p++) { - usz i = indexs[fallback0_count + p]; + for (sz p = 0; p < fallback1_count; p++) { + sz i = indexs[fallback0_count + p]; - usz start_offset = ranges[i]; - usz end_offset = ranges[i + 1]; + sz start_offset = ranges[i]; + sz end_offset = ranges[i + 1]; quicksort_indexed(list, low + start_offset, low + end_offset, compare_fn, ...key_fn); } - for (usz p = 0; p < recursion_count; p++) + for (sz p = 0; p < recursion_count; p++) { - usz i = indexs[255 - p]; + sz i = indexs[255 - p]; - usz start_offset = ranges[i]; - usz end_offset = ranges[i + 1]; + sz start_offset = ranges[i]; + sz end_offset = ranges[i + 1]; $if $defined(key_fn): - csort(list, low + start_offset, low + end_offset, key_fn, byte_idx - 1); + csort(list, low + start_offset, low + end_offset, key_fn, byte_idx - 1u); $else - csort(list, low + start_offset, low + end_offset, byte_idx - 1); + csort(list, low + start_offset, low + end_offset, byte_idx - 1u); $endif } } diff --git a/test/stdlib/src/sort/insertionsort.c3 b/test/stdlib/src/sort/insertionsort.c3 index 18ba4bf..16037c7 100644 --- a/test/stdlib/src/sort/insertionsort.c3 +++ b/test/stdlib/src/sort/insertionsort.c3 @@ -11,7 +11,7 @@ macro void insertionsort(list, cmp = ..., context = ...) @builtin @safemacro { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; - $if $kindof(list) == SLICE: + $if @kindof(list) == SLICE: isort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, lengthof(list), used_cmp, used_ctx); $else isort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, lengthof(*list), used_cmp, used_ctx); @@ -20,15 +20,15 @@ macro void insertionsort(list, cmp = ..., context = ...) @builtin @safemacro module std::sort @private; -fn void isort(ListType list, usz low, usz high, CmpFn comp, Context context) @noinline @private +fn void isort(ListType list, sz low, sz high, CmpFn comp, Context context) @noinline @private { var $has_cmp = $typeof(comp) != TypeNotSet; var $has_context = $typeof(context) != TypeNotSet; - var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) p = list_get(list, 0)); + var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn::params[0].type) p = list_get(list, 0)); var $has_get_ref = IS_SLICE ||| $defined(&(*list)[0]); - for (usz i = low; i < high; ++i) + for (sz i = low; i < high; ++i) { - usz j = i; + sz j = i; for (;j > low;) { $if $has_get_ref: @@ -48,7 +48,7 @@ fn void isort(ListType list, usz low, usz high, CmpFn comp, Context context) @no $endswitch @swap(*rhs, *lhs); $else - usz r = j; + sz r = j; --j; $switch: $case $cmp_by_value && $has_context: diff --git a/test/stdlib/src/sort/mergesort.c3 b/test/stdlib/src/sort/mergesort.c3 new file mode 100644 index 0000000..8095f2b --- /dev/null +++ b/test/stdlib/src/sort/mergesort.c3 @@ -0,0 +1,90 @@ +module std::sort; + +<* + Sort list using the mergesort algorithm. + + @require @list_is_by_ref(list) : "Expected a list passed by reference, or slice passed by value" + @require @is_sortable(list) : "The list must be indexable and support .len or .len()" + @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" + @require @is_valid_context(...cmp, ...context) : "Expected a valid context" + + @param list : "The list to sort" + @param cmp : "The comparison function" + @param context : "The context to use with the comparion function as needed" + @param buffer_allocator : "The allocator to use for allocating a buffer used by merge. Defaults to tmem." +*> +macro void mergesort(list, cmp = ..., context = ..., buffer_allocator = tmem) @builtin +{ + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; + $if @kindof(list) == SLICE: + msort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, used_cmp, used_ctx, buffer_allocator); + $else + msort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, used_cmp, used_ctx, buffer_allocator); + $endif +} + +module std::sort @private; + +fn void msort(ListType list, CmpFn cmp, Context context, Allocator allocator) +{ + const LIST_IS_REF = @kindof(list) == POINTER; + sz len = !LIST_IS_REF ??? lengthof(list) : lengthof(*list); + if (len <= 1) return; + ElementType[] buff = alloc::new_array(allocator, ElementType, len); + msort_split(list, buff, 0, len, cmp, context); + alloc::free(allocator, buff); + return; +} + +<* +@require left <= right : "Indexing has to be done from lower to higher index" +*> +fn void msort_split(ListType list, ElementType[] buff, sz left, sz right, CmpFn cmp, Context context) +{ + if (right - left <= 1) return; + sz mid = left + (right - left) / 2; + msort_split(list, buff, left, mid, cmp, context); + msort_split(list, buff, mid, right, cmp, context); + merge(list, buff, left, mid, right, cmp, context); +} + +<* +@require left <= mid : "Indexing has to be done from lower to higher index" +@require mid <= right : "Indexing has to be done from lower to higher index" +*> +fn void merge(ListType list, ElementType[] buff, sz left, sz mid, sz right, CmpFn cmp, Context context) +{ + var $has_cmp = $typeof(cmp) != TypeNotSet; + var $has_context = $typeof(context) != TypeNotSet; + var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn::params[0].type) v = list_get(list, 0)); + + sz i = left; + sz j = mid; + sz k = left; + while(i < mid && j < right){ + $switch: + $case $cmp_by_value && $has_context: + bool cmp_result = cmp(list_get(list, i), list_get(list, j), context) <= 0; + $case $cmp_by_value: + bool cmp_result = cmp(list_get(list, i), list_get(list, j)) <= 0; + $case $has_cmp && $has_context: + bool cmp_result = cmp(list_get_ref(list, i), list_get_ref(list, j), context) <= 0; + $case $has_cmp: + bool cmp_result = cmp(list_get_ref(list, i), list_get_ref(list, j)) <= 0; + $default: + bool cmp_result = less_eq(list_get(list, i), list_get(list, j)); + $endswitch + if (cmp_result) + { + buff[k++] = list_get(list, i++); + } else + { + buff[k++] = list_get(list, j++); + } + } + + while (i < mid) buff[k++] = list_get(list, i++); + while (j < right) buff[k++] = list_get(list, j++); + mem::copy(list_get_ref(list, left), &buff[left], (right - left) * @sizeof(list_get(list, 0))); +} diff --git a/test/stdlib/src/sort/quicksort.c3 b/test/stdlib/src/sort/quicksort.c3 index f54dbf4..b79a1e5 100644 --- a/test/stdlib/src/sort/quicksort.c3 +++ b/test/stdlib/src/sort/quicksort.c3 @@ -12,10 +12,10 @@ macro void quicksort(list, cmp = ..., context = ...) @builtin { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; - $if $kindof(list) == SLICE: - qsort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)list.len - 1, used_cmp, used_ctx); + $if @kindof(list) == SLICE: + qsort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (sz)list.len - 1, used_cmp, used_ctx); $else - qsort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)lengthof(*list) - 1, used_cmp, used_ctx); + qsort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (sz)lengthof(*list) - 1, used_cmp, used_ctx); $endif } @@ -29,14 +29,14 @@ macro void quicksort(list, cmp = ..., context = ...) @builtin @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "expected a comparison function which compares values" @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro quickselect(list, isz k, cmp = ..., context = ...) @builtin +macro quickselect(list, sz k, cmp = ..., context = ...) @builtin { var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; var used_ctx = $defined(context) ??? context : (TypeNotSet)null; - $if $kindof(list) == SLICE: - return qselect{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)list.len - 1, k, used_cmp, used_ctx); + $if @kindof(list) == SLICE: + return qselect{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (sz)list.len - 1, k, used_cmp, used_ctx); $else - return qselect{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)lengthof(*list) - 1, k, used_cmp, used_ctx); + return qselect{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (sz)lengthof(*list) - 1, k, used_cmp, used_ctx); $endif } @@ -45,24 +45,24 @@ module std::sort @private; struct StackElementItem @private { - isz low; - isz high; + sz low; + sz high; } alias Stack @private = StackElementItem[64]; // Based on https://alienryderflex.com/quicksort by Darel Rex Finley, Public Domain. -fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) @noinline +fn void qsort(ListType list, sz low, sz high, CmpFn cmp, Context context) @noinline { if (low >= 0 && high >= 0 && low < high) { Stack stack; stack[0].low = low; stack[0].high = high; - isz i; - isz l; - isz h; + sz i; + sz l; + sz h; while (i >= 0) { l = stack[i].low; @@ -91,15 +91,15 @@ fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) @noi @require low <= k : "kth smallest element is smaller than lower bounds" @require k <= high : "kth smallest element is larger than upper bounds" *> -fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Context context) @noinline +fn ElementType? qselect(ListType list, sz low, sz high, sz k, CmpFn cmp, Context context) @noinline { if (low >= 0 && high >= 0 && low < high) { - isz l = low; - isz h = high; - isz pivot; + sz l = low; + sz h = high; + sz pivot; - usz max_retries = 64; + sz max_retries = 64; while (l <= h && max_retries--) { pivot = @partition(list, l, h, cmp, context); @@ -117,11 +117,11 @@ fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Cont return NOT_FOUND~; } -macro isz @partition(ListType list, isz l, isz h, CmpFn cmp, Context context) +macro sz @partition(ListType list, sz l, sz h, CmpFn cmp, Context context) { var $has_cmp = $typeof(cmp) != sort::TypeNotSet; var $has_context = $typeof(context) != sort::TypeNotSet; - var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) v = list_get(list, 0)); + var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn::params[0].type) v = list_get(list, 0)); ElementType pivot = list_get(list, l); while (l < h) diff --git a/test/stdlib/src/sort/sort.c3 b/test/stdlib/src/sort/sort.c3 index 1ae2f66..afd6dcc 100644 --- a/test/stdlib/src/sort/sort.c3 +++ b/test/stdlib/src/sort/sort.c3 @@ -5,7 +5,7 @@ typedef TypeNotSet @private = void*; macro bool @list_is_by_ref(#list) @const { - return $kindof(#list) == SLICE ||| ($kindof(#list) == POINTER &&& $kindof(*#list) != SLICE); + return @kindof(#list) == SLICE ||| (@kindof(#list) == POINTER &&& @kindof(*#list) != SLICE); } <* @@ -14,7 +14,7 @@ macro bool @list_is_by_ref(#list) @const macro bool @is_sortable(#list) @const { $switch: - $case $kindof(#list) == SLICE: + $case @kindof(#list) == SLICE: return true; $case !$defined(*#list): return false; @@ -31,7 +31,7 @@ macro bool @is_sortable(#list) @const macro bool @is_any_sortable(#list) @const { - $switch $kindof(#list): + $switch @kindof(#list): $case SLICE: return true; $case POINTER: @@ -59,9 +59,9 @@ macro bool @is_valid_cmp_fn(#cmp = ..., #list = ..., #context = ...) @const var $Type = $typeof(#cmp); var $no_context = !$defined(#context); $switch: - $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; + $case $Type::kind != FUNC ||| $Type::returns.kind != SIGNED_INT: return false; $default: - $if $kindof(#list) == SLICE: + $if @kindof(#list) == SLICE: $switch: $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; @@ -90,9 +90,9 @@ macro bool @is_any_valid_cmp_fn(#cmp = ..., #list = ..., #context = ...) @const var $Type = $typeof(#cmp); var $no_context = !$defined(#context); $switch: - $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; + $case $Type::kind != FUNC ||| $Type::returns.kind != SIGNED_INT: return false; $default: - $if $kindof(#list) != POINTER: + $if @kindof(#list) != POINTER: $switch: $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; @@ -122,10 +122,10 @@ macro bool @is_cmp_key_fn(#key_fn = ..., #list = ...) @const return true; $else $switch: - $case $kindof(#key_fn) != FUNC: return false; - $case $typeof(#key_fn).returns.kindof != UNSIGNED_INT: return false; + $case @kindof(#key_fn) != FUNC: return false; + $case $typeof(#key_fn)::returns.kind != UNSIGNED_INT: return false; $default: - $if $kindof(#list) == SLICE: + $if @kindof(#list) == SLICE: $switch: $case $defined(#key_fn((#list)[0])): return true; $case $defined(#key_fn(&&((#list)[0]))): return true; diff --git a/test/stdlib/src/sort/sort_common_private.c3 b/test/stdlib/src/sort/sort_common_private.c3 index ee57d56..ceec4a5 100644 --- a/test/stdlib/src/sort/sort_common_private.c3 +++ b/test/stdlib/src/sort/sort_common_private.c3 @@ -1,7 +1,7 @@ module std::sort @private; alias ElementType = $typeof(((Type){})[0]); -const bool IS_SLICE = Type.kindof == SLICE; +const bool IS_SLICE = Type::kind == SLICE; alias ListType = $typefrom(IS_SLICE ??? Type : Type*); macro ElementType list_get(ListType l, i) @if(!IS_SLICE) => (*l)[i]; macro ElementType list_get(ListType l, i) @if(IS_SLICE) => l[i]; diff --git a/test/stdlib/src/sort/sorted.c3 b/test/stdlib/src/sort/sorted.c3 index e1bb333..50cef78 100644 --- a/test/stdlib/src/sort/sorted.c3 +++ b/test/stdlib/src/sort/sorted.c3 @@ -10,16 +10,16 @@ module std::sort; macro bool is_sorted(list, cmp = ..., ctx = ...) @builtin { // When the context or cmp functions are not defined, we can simply use a dummy/default type. - const LIST_IS_REF = $kindof(list) == POINTER; + const LIST_IS_REF = @kindof(list) == POINTER; var $Type = !LIST_IS_REF ??? $typeof(list) : $typeof(*list); - usz len = !LIST_IS_REF ??? lengthof(list) : lengthof(*list); + sz len = !LIST_IS_REF ??? lengthof(list) : lengthof(*list); if (len <= 1) return true; bool is_sorted = false; if CHECK_SORT: (true) { - usz i = 0; + sz i = 0; int sort_order; // determine sort order (ascending or descending) for (; i < (len - 1) && sort_order == 0; i++) @@ -45,10 +45,10 @@ macro bool is_sorted(list, cmp = ..., ctx = ...) @builtin macro int @sort_cmp(list_maybe_ref, pos, cmp = ..., ctx = ...) @local { - var list = $kindof(list_maybe_ref) != POINTER ??? list_maybe_ref : *list_maybe_ref; + var list = @kindof(list_maybe_ref) != POINTER ??? list_maybe_ref : *list_maybe_ref; var $has_cmp = $defined(cmp); var $has_context = $defined(ctx); - var $cmp_by_value = $has_cmp &&& $defined($typefrom($typeof(cmp).paramsof[0].type) v = list[0]); + var $cmp_by_value = $has_cmp &&& $defined($typefrom($typeof(cmp)::params[0].type) v = list[0]); var a = list[pos]; var b = list[pos+1]; diff --git a/test/stdlib/src/threads/buffered_channel.c3 b/test/stdlib/src/threads/buffered_channel.c3 index d886877..32741ac 100644 --- a/test/stdlib/src/threads/buffered_channel.c3 +++ b/test/stdlib/src/threads/buffered_channel.c3 @@ -1,41 +1,37 @@ module std::thread::channel ; -typedef BufferedChannel = void*; +typedef BufferedChannel = void; struct BufferedChannelImpl @private { Allocator allocator; Mutex mu; bool closed; - usz size; - usz elems; + sz size; + sz elems; - usz sendx; - usz send_waiting; + sz sendx; + sz send_waiting; ConditionVariable send_cond; - usz readx; - usz read_waiting; + sz readx; + sz read_waiting; ConditionVariable read_cond; Type[*] buf; } + <* @require size > 0: "channel size must be > 0" *> -fn void? BufferedChannel.init(&self, Allocator allocator, usz size = 1) +fn BufferedChannel*? create_buffered(Allocator allocator, sz size = 1) { - BufferedChannelImpl* channel = allocator::new_with_padding(allocator, BufferedChannelImpl, Type.sizeof * size)!; - defer catch allocator::free(allocator, channel); + BufferedChannelImpl* channel = alloc::new_with_padding(allocator, BufferedChannelImpl, Type::size * size)!; + defer catch alloc::free(allocator, channel); channel.allocator = allocator; channel.size = size; - channel.elems = 0; - channel.sendx = 0; - channel.send_waiting = 0; - channel.readx = 0; - channel.read_waiting = 0; channel.mu.init()!; defer catch channel.mu.destroy(); @@ -44,28 +40,25 @@ fn void? BufferedChannel.init(&self, Allocator allocator, usz size = 1) channel.read_cond.init()!; defer catch channel.read_cond.destroy(); - *self = (BufferedChannel)channel; + return (BufferedChannel*)channel; } -fn void? BufferedChannel.destroy(&self) @maydiscard // Remove optional in 0.8.0 +fn void BufferedChannel.destroy(&self) { - BufferedChannelImpl* channel = (BufferedChannelImpl*)(*self); + BufferedChannelImpl* channel = (BufferedChannelImpl*)self; channel.mu.destroy(); channel.send_cond.destroy(); channel.read_cond.destroy(); - allocator::free(channel.allocator, channel); - - *self = null; - + alloc::free(channel.allocator, channel); } -fn void? BufferedChannel.push(self, Type val) +fn void? BufferedChannel.push(&self, Type val) { BufferedChannelImpl* channel = (BufferedChannelImpl*)self; channel.mu.lock(); - defer catch channel.mu.unlock(); + defer channel.mu.unlock(); // if channel is full -> wait while (channel.elems == channel.size && !channel.closed) @@ -96,16 +89,14 @@ fn void? BufferedChannel.push(self, Type val) { channel.read_cond.signal(); } - - channel.mu.unlock(); } -fn Type? BufferedChannel.pop(self) +fn Type? BufferedChannel.pop(&self) { BufferedChannelImpl* channel = (BufferedChannelImpl*)self; channel.mu.lock(); - defer catch channel.mu.unlock(); + defer channel.mu.unlock(); // if chan is empty -> wait for sender while (channel.elems == 0 && !channel.closed) @@ -140,12 +131,10 @@ fn Type? BufferedChannel.pop(self) channel.send_cond.signal(); } - channel.mu.unlock(); - return ret; } -fn void? BufferedChannel.close(self) @maydiscard // Remove optional in 0.8.0 +fn void BufferedChannel.close(&self) { BufferedChannelImpl* channel = (BufferedChannelImpl*)self; diff --git a/test/stdlib/src/threads/event/event_thread.c3 b/test/stdlib/src/threads/event/event_thread.c3 index c14cce0..2ff20ba 100644 --- a/test/stdlib/src/threads/event/event_thread.c3 +++ b/test/stdlib/src/threads/event/event_thread.c3 @@ -180,7 +180,7 @@ fn EventThreadPool* EventThreadPool.init(&self, int size, String name, Allocator self.threads.init(allocator: allocator); for (int i = 0; i < size; i++) { - EventThread* thread = allocator::new(allocator, EventThread, { .is_alive = true, .state = FOLLOWER, .pool = null, .queue = null /* pool.getQueue() */, }); + EventThread* thread = alloc::new(allocator, EventThread, { .is_alive = true, .state = FOLLOWER, .pool = null, .queue = null /* pool.getQueue() */, }); thread.name = string::format("%s:%d", name, i, allocator: allocator); self.threads.push(thread); if (self.leader) diff --git a/test/stdlib/src/threads/fixed_pool.c3 b/test/stdlib/src/threads/fixed_pool.c3 index e0cdacf..2cc80bd 100644 --- a/test/stdlib/src/threads/fixed_pool.c3 +++ b/test/stdlib/src/threads/fixed_pool.c3 @@ -12,9 +12,9 @@ struct FixedThreadPool { Mutex mu; QueueItem[] queue; - usz qindex; - usz qworking; - usz num_threads; + sz qindex; + sz qworking; + sz num_threads; bitstruct : char { bool initialized; bool stop; @@ -37,7 +37,7 @@ struct QueueItem @private @require threads > 0 && threads < 0x1000 : `Threads should be greater than 0 and less than 0x1000` @require queue_size < 0x10000 : `Queue size must be less than 65536` *> -fn void? FixedThreadPool.init(&self, usz threads, usz queue_size = 0) +fn void? FixedThreadPool.init(&self, sz threads, sz queue_size = 0, ThreadSettings thread_settings = {}) { if (queue_size == 0) queue_size = threads * 32; defer catch @ok(self.destroy()); @@ -56,7 +56,7 @@ fn void? FixedThreadPool.init(&self, usz threads, usz queue_size = 0) defer catch self.collect.destroy(); foreach (&thread : self.pool) { - thread.create(&process_work, self)!; + thread.create(&process_work, self, thread_settings)!; // The thread resources will be cleaned up when the thread exits. thread.detach()!; } @@ -65,7 +65,7 @@ fn void? FixedThreadPool.init(&self, usz threads, usz queue_size = 0) <* Join all threads in the pool. *> -fn void? FixedThreadPool.join(&self) @maydiscard // Remove optional in 0.8.0 +fn void FixedThreadPool.join(&self) { if (self.initialized) { @@ -94,7 +94,7 @@ fn void? FixedThreadPool.destroy(&self) Stop all the threads and cleanup the pool. Any pending work will be processed. *> -fn void? FixedThreadPool.stop_and_destroy(&self) @maydiscard // Remove optional in 0.8.0 +fn void FixedThreadPool.stop_and_destroy(&self) { self.@shutdown(self.stop); } @@ -142,7 +142,7 @@ fn void? FixedThreadPool.push(&self, ThreadPoolFn func, args...) if (args.len) { data = mem::alloc_array(any, args.len); - foreach (i, arg : args) data[i] = allocator::clone_any(mem, arg); + foreach (i, arg : args) data[i] = alloc::clone_any(mem, arg); } self.queue[self.qindex] = { .func = func, .args = data }; self.qindex++; diff --git a/test/stdlib/src/threads/oneshot_channel.c3 b/test/stdlib/src/threads/oneshot_channel.c3 new file mode 100644 index 0000000..fe37c6c --- /dev/null +++ b/test/stdlib/src/threads/oneshot_channel.c3 @@ -0,0 +1,123 @@ +module std::thread::channel ; + +typedef OneShotChannel = void; + +struct OneShotChannelImpl @private +{ + Allocator allocator; + Mutex mu; + ConditionVariable cond; + Type value; + bool sent; + bool consumed; + bool closed; +} + +<* + Initialize a one-shot channel for sending exactly one value between threads. + + The first successful `push` stores the value, and the first successful `pop` + receives it. Closing the channel wakes blocked receivers; if a value was + already sent before closing, it remains receivable once. + + Example: + ```c3 + OneShotChannel{int}* channel = channel::new_one_shot(mem); + defer channel.destroy(); + + Thread thread; + thread.create(fn int(void* arg) + { + OneShotChannel{int}* channel = arg; + channel.push(123)!!; + return 0; + }, (void*)channel)!!; + defer thread.join(); + + int value = channel.pop()!!; + ``` +*> +fn OneShotChannel*? create_one_shot(Allocator allocator) +{ + OneShotChannelImpl* channel = alloc::new(allocator, OneShotChannelImpl, { .allocator = allocator }); + defer catch alloc::free(allocator, channel); + + channel.allocator = allocator; + channel.mu.init()!; + defer catch channel.mu.destroy(); + channel.cond.init()!; + + return (OneShotChannel*)channel; +} + +fn void OneShotChannel.destroy(&self) +{ + OneShotChannelImpl* channel = (OneShotChannelImpl*)self; + channel.mu.destroy(); + channel.cond.destroy(); + alloc::free(channel.allocator, channel); +} + +<* + Send the one value for this channel. + + @return? thread::CHANNEL_CLOSED : "If the channel was closed or already has a value." +*> +fn void? OneShotChannel.push(&self, Type val) +{ + OneShotChannelImpl* channel = (OneShotChannelImpl*)self; + + channel.mu.lock(); + defer catch channel.mu.unlock(); + + if (channel.closed || channel.sent) return thread::CHANNEL_CLOSED~; + + channel.value = val; + channel.sent = true; + channel.cond.broadcast(); + channel.mu.unlock(); +} + +<* + Receive the value from the channel. + + Blocks until a value is sent or the channel is closed. If the value was sent + before closing, it can still be received exactly once. + + @return? thread::CHANNEL_CLOSED : "If the channel was closed before sending, or the value was already consumed." +*> +fn Type? OneShotChannel.pop(&self) +{ + OneShotChannelImpl* channel = (OneShotChannelImpl*)self; + + channel.mu.lock(); + defer catch channel.mu.unlock(); + + while (!channel.sent && !channel.closed) + { + channel.cond.wait(&channel.mu); + } + + if (!channel.sent || channel.consumed) + { + return thread::CHANNEL_CLOSED~; + } + + Type value = channel.value; + channel.consumed = true; + channel.mu.unlock(); + return value; +} + +<* + Close the channel and wake any blocked receivers. +*> +fn void OneShotChannel.close(&self) +{ + OneShotChannelImpl* channel = (OneShotChannelImpl*)self; + + channel.mu.lock(); + channel.closed = true; + channel.cond.broadcast(); + channel.mu.unlock(); +} diff --git a/test/stdlib/src/threads/os/cpu.c3 b/test/stdlib/src/threads/os/cpu.c3 index b5c99f4..d0425f5 100644 --- a/test/stdlib/src/threads/os/cpu.c3 +++ b/test/stdlib/src/threads/os/cpu.c3 @@ -38,7 +38,7 @@ const HW_L3SETTINGS = 21; /* int: L3 Cache Settings */ const HW_L3CACHESIZE = 22; /* int: L3 Cache Size in Bytes */ const HW_MAXID = 23; /* number of valid hw ids */ -fn uint native_cpu() +fn int native_cpu() { int[2] nm; usz len = 4; @@ -47,40 +47,40 @@ fn uint native_cpu() nm = { CTL_HW, HW_NCPU }; libc::sysctl(&nm, 2, &count, &len, null, 0); if (count < 1) count = 1; - return count; + return (int)count; } module std::thread::cpu @if(env::OPENBSD); import std::os::openbsd; import libc; -fn uint native_cpu() +fn int native_cpu() { uint ncpu; - usz len = uint.sizeof; + usz len = uint::size; int[2] mib = { openbsd::CTL_HW, openbsd::HW_NCPU }; libc::sysctl(&mib, 2, &ncpu, &len, null, 0); - return max(1, ncpu); + return (int)max(1, ncpu); } module std::thread::cpu @if(env::LINUX); import libc; -fn uint native_cpu() +fn int native_cpu() { - return libc::get_nprocs_conf(); + return (int)libc::get_nprocs_conf(); } module std::thread::cpu @if(env::WIN32); import libc; -fn uint native_cpu() +fn int native_cpu() { SystemInfo info; libc::get_system_info(&info); - return info.dwNumberOfProcessors; + return (int)info.dwNumberOfProcessors; } module std::thread::cpu @if(!env::WIN32 && !env::LINUX && !env::OPENBSD && ! env::DARWIN); @@ -89,4 +89,4 @@ import libc; <* For environments with an undefined way of fetching the number of CPUs, simply return 1. *> -fn uint native_cpu() => 1; +fn int native_cpu() => 1; diff --git a/test/stdlib/src/threads/os/thread_none.c3 b/test/stdlib/src/threads/os/thread_none.c3 index a8ace5d..5314528 100644 --- a/test/stdlib/src/threads/os/thread_none.c3 +++ b/test/stdlib/src/threads/os/thread_none.c3 @@ -1,4 +1,5 @@ module std::thread::os @if (!env::NATIVE_THREADING); +import libc; typedef NativeMutex = int; typedef NativeTimedMutex = int; @@ -6,6 +7,20 @@ typedef NativeConditionVariable = int; typedef NativeOnceFlag = int; typedef NativeThread = int; +fn NativeThread native_thread_current() +{ + return {}; +} + +fn void native_thread_exit(int result) +{ + $if env::NO_LIBC: + $$trap(); + $else + libc::exit(result); + $endif +} + fn void NativeOnceFlag.call_once(&flag, OnceFn func) { if (*flag == (NativeOnceFlag)0) @@ -15,6 +30,8 @@ fn void NativeOnceFlag.call_once(&flag, OnceFn func) } } +fn bool NativeThread.equals(thread, NativeThread other) => true; + fn void? NativeMutex.init(&mtx, MutexType type) => NOT_IMPLEMENTED~; fn bool NativeMutex.is_initialized(&self) diff --git a/test/stdlib/src/threads/os/thread_posix.c3 b/test/stdlib/src/threads/os/thread_posix.c3 index 44a0dc1..cd8f613 100644 --- a/test/stdlib/src/threads/os/thread_posix.c3 +++ b/test/stdlib/src/threads/os/thread_posix.c3 @@ -1,9 +1,8 @@ module std::thread::os @if(env::POSIX); import std::os::posix, std::time, libc; -import libc::errno; import std::thread; - +const ANDROID_LINUX = env::LINUX || env::ANDROID; struct NativeMutex { @@ -17,9 +16,11 @@ alias NativeConditionVariable = Pthread_cond_t; struct NativeThread { - inline Pthread_t pthread; + Pthread_t pthread; ThreadFn thread_fn; void* arg; + Sem_t sem @if (ANDROID_LINUX); + Pid_t tid @if (ANDROID_LINUX); } alias NativeOnceFlag = Pthread_once_t; @@ -70,9 +71,9 @@ fn void NativeMutex.lock(&self) { switch (posix::pthread_mutex_lock(&self.mutex)) { - case errno::EINVAL: unreachable("Mutex invalid"); - case errno::EDEADLK: abort("Mutex deadlock"); - case errno::OK: return; + case EINVAL: unreachable("Mutex invalid"); + case EDEADLK: abort("Mutex deadlock"); + case OK: return; default: unreachable("Unexpected error in lock"); } } @@ -80,23 +81,23 @@ fn void NativeMutex.lock(&self) <* @require self.is_initialized() : "Mutex was not initialized" *> -fn void? NativeMutex.lock_timeout(&self, ulong ms) +fn void? NativeMutex.lock_timeout(&self, long ms) { /* Try to acquire the lock and, if we fail, sleep for 5ms. */ Errno result; - while ((result = posix::pthread_mutex_trylock(&self.mutex)) == errno::EBUSY) + while ((result = posix::pthread_mutex_trylock(&self.mutex)) == EBUSY) { if (!ms) break; - ulong sleep = min(5, ms); + long sleep = min(5, ms); if (!libc::nanosleep(&&time::ms(ms).to_timespec(), null)) return thread::LOCK_TIMEOUT~; ms -= sleep; } switch (result) { - case errno::OK: + case OK: return; - case errno::EBUSY: - case errno::ETIMEDOUT: + case EBUSY: + case ETIMEDOUT: return thread::LOCK_TIMEOUT~; default: return unreachable("Invalid lock %d", result); @@ -151,7 +152,7 @@ fn void NativeConditionVariable.wait(&cond, NativeMutex* mtx) @require mtx.is_initialized() @return? thread::WAIT_TIMEOUT *> -fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) +fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, long ms) { Time time = time::now() + time::ms(ms); return cond.wait_until(mtx, time) @inline; @@ -176,9 +177,9 @@ fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) { switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &&time.to_timespec())) { - case errno::ETIMEDOUT: + case ETIMEDOUT: return thread::WAIT_TIMEOUT~; - case errno::OK: + case OK: return; default: $if(env::OPENBSD): @@ -196,17 +197,81 @@ fn void* callback(void* arg) @private { NativeThread* thread = arg; current_thread = *thread; + $if ANDROID_LINUX: + thread.tid = (Pid_t)@syscall(libc::SYS_GETTID); + os::sem_post(&thread.sem); + $endif return (void*)(iptr)thread.thread_fn(thread.arg); } -fn void? NativeThread.create(&thread, ThreadFn thread_fn, void* arg) +fn bool NativeThread.set_priority(&self, ThreadPriority priority) +{ + if (priority == DEFAULT) priority = NORMAL; + $switch: + $case ANDROID_LINUX: + if (priority == REALTIME) + { + Pthread_sched_param param = { .sched_priority = 1 }; + return !posix::pthread_setschedparam(self.pthread, libc::SCHED_FIFO, ¶m); + } + if (!self.tid) return false; + const CInt[4] NICE_TABLE = { 19, 5, 0, -5 }; + return !libc::setpriority(libc::PRIO_PROCESS, (CInt)self.tid, NICE_TABLE[priority.ordinal - 1]); + $case $defined(libc::SCHED_OTHER, libc::sched_get_priority_min): + Pthread_sched_param param @noinit; + if (priority == REALTIME) + { + $if env::DARWIN: + param = { .sched_priority = libc::sched_get_priority_max(libc::SCHED_FIFO) / 2 }; + $else + param = { .sched_priority = libc::sched_get_priority_max(libc::SCHED_FIFO) }; + $endif + return !posix::pthread_setschedparam(self.pthread, libc::SCHED_FIFO, ¶m); + } + else + { + int low = libc::sched_get_priority_min(libc::SCHED_OTHER); + int high = libc::sched_get_priority_max(libc::SCHED_OTHER); + param = { .sched_priority = (low + ((high - low) * (priority.ordinal - 1)) / 4) }; + } + return !posix::pthread_setschedparam(self.pthread, libc::SCHED_OTHER, ¶m); + $default: + return false; + $endswitch +} + +<* + @require settings.stack_size >= 0 + @return? thread::INIT_FAILED, thread::INVALID_THREAD_SETTINGS +*> +fn void? NativeThread.create(&thread, ThreadFn thread_fn, void* arg, ThreadSettings settings) { thread.thread_fn = thread_fn; thread.arg = arg; + Pthread_attr_t attr; + Pthread_attr_t* attr_ptr = null; + defer if (attr_ptr) posix::pthread_attr_destroy(attr_ptr); + if (settings.stack_size > 0) + { + if (posix::pthread_attr_init(&attr)) return thread::INIT_FAILED~; + attr_ptr = &attr; + if (settings.stack_size > 0) + { + if (posix::pthread_attr_setstacksize(attr_ptr, settings.stack_size)) return thread::INVALID_THREAD_SETTINGS~; + } + } + $if ANDROID_LINUX: + os::sem_init(&thread.sem, 0, 0); + defer os::sem_destroy(&thread.sem); + $endif if (posix::pthread_create(&thread.pthread, null, &callback, thread) != 0) { return thread::INIT_FAILED~; } + $if ANDROID_LINUX: + os::sem_wait(&thread.sem); + $endif + if (settings.priority != DEFAULT) thread.set_priority(settings.priority); } fn void NativeThread.detach(thread) @@ -219,8 +284,13 @@ fn void native_thread_exit(int result) posix::pthread_exit((void*)(iptr)result); } + fn NativeThread native_thread_current() { + if (!current_thread.thread_fn) + { + current_thread = { .pthread = posix::pthread_self(), .thread_fn = fn(void*) => 0 }; + } return current_thread; } @@ -238,10 +308,10 @@ fn int? NativeThread.join(thread) @maydiscard void *pres; switch (posix::pthread_join(thread.pthread, &pres)) { - case errno::OK: return (int)(iptr)pres; - case errno::EINVAL: unreachable("Thread is not joinable."); - case errno::EDEADLK: unreachable("Thread join from current thread."); - case errno::ESRCH: return thread::THREAD_NOT_FOUND~; + case OK: return (int)(iptr)pres; + case EINVAL: unreachable("Thread is not joinable."); + case EDEADLK: unreachable("Thread join from current thread."); + case ESRCH: return thread::THREAD_NOT_FOUND~; default: unreachable("Thread join returned unexpected result"); } } diff --git a/test/stdlib/src/threads/os/thread_win32.c3 b/test/stdlib/src/threads/os/thread_win32.c3 index 2f3d270..ebf2b30 100644 --- a/test/stdlib/src/threads/os/thread_win32.c3 +++ b/test/stdlib/src/threads/os/thread_win32.c3 @@ -179,7 +179,7 @@ fn void NativeTimedMutex.lock(&mtx) @require mtx.initialized : "Mutex was not initialized" @return? thread::WAIT_TIMEOUT *> -fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms) +fn void? NativeTimedMutex.lock_timeout(&mtx, long ms) { Win32_DWORD current_thread = win32::getCurrentThreadId(); if (mtx.owner_thread == current_thread) @@ -204,8 +204,8 @@ fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms) Clock start = clock::now(); for (NanoDuration remaining = duration; remaining > time::NANO_DURATION_ZERO; remaining = duration - start.to_now()) { - ulong remaining_ms = remaining.to_ms(); - if (remaining_ms > uint.max) remaining_ms = uint.max; + long remaining_ms = remaining.to_ms(); + if (remaining_ms > int::max) remaining_ms = int::max; _wait_cond_var(mtx, (uint)remaining_ms)!; if (!mtx.locks) { @@ -325,7 +325,7 @@ fn void NativeConditionVariable.wait(&cond, NativeMutex* mtx) @inline *> fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) @inline { - if (ms > uint.max) ms = uint.max; + if (ms > uint::max) ms = uint::max; if (!timedwait(cond, mtx, (uint)ms) @inline) return thread::WAIT_TIMEOUT~; } @@ -337,7 +337,7 @@ fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, { if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT~; long ms = duration.to_ms(); - if (ms > uint.max) ms = uint.max; + if (ms > uint::max) ms = uint::max; if (!timedwait(cond, mtx, (uint)ms) @inline) return thread::WAIT_TIMEOUT~; } @@ -351,9 +351,25 @@ fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) return cond.wait_timeout_duration(mtx, duration); } -fn void? NativeThread.create(&thread, ThreadFn func, void* args) +fn void? NativeThread.create(&thread, ThreadFn func, void* args, ThreadSettings settings) { - if (!(*thread = (NativeThread)win32::createThread(null, 0, func, args, 0, null))) return thread::INIT_FAILED~; + NativeThread t = (NativeThread)win32::createThread(null, settings.stack_size, func, args, settings.stack_size > 0 ? win32::STACK_SIZE_PARAM_IS_A_RESERVATION : 0, null); + if (!t) return thread::INIT_FAILED~; + *thread = t; + if (settings.priority != DEFAULT) t.set_priority(settings.priority); +} + +fn bool NativeThread.set_priority(&self, ThreadPriority priority) +{ + const int[ThreadPriority::len] TABLE = { + win32::THREAD_PRIORITY_NORMAL, + win32::THREAD_PRIORITY_IDLE, + win32::THREAD_PRIORITY_BELOW_NORMAL, + win32::THREAD_PRIORITY_NORMAL, + win32::THREAD_PRIORITY_ABOVE_NORMAL, + win32::THREAD_PRIORITY_TIME_CRITICAL + }; + return (bool)win32::setThreadPriority(*self, TABLE[priority.ordinal - 1]); } fn void NativeThread.detach(thread) @inline @@ -389,10 +405,10 @@ fn void NativeOnceFlag.call_once(&flag, OnceFn func) fn int NativeThread.join(thread) { uint res; - if (win32::waitForSingleObject(thread, win32::INFINITE) == win32::WAIT_FAILED) unreachable("Failed to join thread, received wait failed."); - if (!win32::getExitCodeThread(thread, &res)) unreachable("Failed to retrieve exit code when joining."); + if (win32::waitForSingleObject(thread, win32::INFINITE) == win32::WAIT_FAILED) abort("Failed to join thread, received wait failed."); + if (!win32::getExitCodeThread(thread, &res)) abort("Failed to retrieve exit code when joining."); defer win32::closeHandle(thread); - return res; + return (int)res; } fn NativeThread native_thread_current() @@ -409,6 +425,6 @@ fn void? native_sleep_nano(NanoDuration ns) { long ms = ns.to_ms(); if (ms <= 0) return; - if (ms > Win32_DWORD.max) ms = Win32_DWORD.max; + if (ms > Win32_DWORD::max) ms = Win32_DWORD::max; if (win32::sleepEx((Win32_DWORD)ms, (Win32_BOOL)true) == win32::WAIT_IO_COMPLETION) return thread::INTERRUPTED~; } diff --git a/test/stdlib/src/threads/pool.c3 b/test/stdlib/src/threads/pool.c3 index 817472d..abc9403 100644 --- a/test/stdlib/src/threads/pool.c3 +++ b/test/stdlib/src/threads/pool.c3 @@ -5,9 +5,9 @@ struct ThreadPool { Mutex mu; QueueItem[SIZE] queue; - usz qindex; - usz qworking; - usz num_threads; + sz qindex; + sz qworking; + sz num_threads; bitstruct : char { bool initialized; @@ -30,7 +30,7 @@ struct QueueItem @private <* @require !self.initialized : "ThreadPool must not be already initialized" *> -fn void? ThreadPool.init(&self) +fn void? ThreadPool.init(&self, ThreadSettings thread_settings = {}) { defer catch self.destroy(); *self = { .num_threads = SIZE, .initialized = true }; @@ -39,7 +39,7 @@ fn void? ThreadPool.init(&self) self.collect.init()!; foreach (&thread : self.pool) { - thread.create(&process_work, self)!; + thread.create(&process_work, self, thread_settings)!; // The thread resources will be cleaned up when the thread exits. thread.detach()!; } @@ -48,7 +48,7 @@ fn void? ThreadPool.init(&self) <* Join all threads in the pool. *> -fn void? ThreadPool.join(&self) @maydiscard // Remove optional in 0.8.0 +fn void ThreadPool.join(&self) { if (self.initialized) { @@ -65,7 +65,7 @@ fn void? ThreadPool.join(&self) @maydiscard // Remove optional in 0.8.0 Stop all the threads and cleanup the pool. Any pending work will be dropped. *> -fn void? ThreadPool.destroy(&self) @maydiscard // Remove optional in 0.8.0 +fn void ThreadPool.destroy(&self) { self.@shutdown(self.stop_now); } @@ -74,7 +74,7 @@ fn void? ThreadPool.destroy(&self) @maydiscard // Remove optional in 0.8.0 Stop all the threads and cleanup the pool. Any pending work will be processed. *> -fn void? ThreadPool.stop_and_destroy(&self) @maydiscard // Remove optional in 0.8.0 +fn void ThreadPool.stop_and_destroy(&self) { self.@shutdown(self.stop); } @@ -109,7 +109,7 @@ macro void ThreadPool.@shutdown(&self, #stop) @private Push a new job to the pool. Returns whether the queue is full, in which case the job is ignored. *> -fn void? ThreadPool.push(&self, ThreadFn func, void* arg) @maydiscard // Remove optional in 0.8.0 +fn void ThreadPool.push(&self, ThreadFn func, void* arg) { while (true) { diff --git a/test/stdlib/src/threads/thread.c3 b/test/stdlib/src/threads/thread.c3 index 5a671c0..ce29702 100644 --- a/test/stdlib/src/threads/thread.c3 +++ b/test/stdlib/src/threads/thread.c3 @@ -21,36 +21,44 @@ alias ThreadFn = fn int(void* arg); faultdef INIT_FAILED, + INVALID_THREAD_SETTINGS, LOCK_TIMEOUT, WAIT_TIMEOUT, THREAD_NOT_FOUND, INTERRUPTED, CHANNEL_CLOSED; -faultdef - DETACH_FAILED @deprecated, - UNLOCK_FAILED @deprecated, - DESTROY_FAILED @deprecated, - SIGNAL_FAILED @deprecated, - JOIN_FAILED @deprecated, - LOCK_FAILED @deprecated, - WAIT_FAILED @deprecated; +enum ThreadPriority +{ + DEFAULT, + BACKGROUND, + LOW, + NORMAL, + HIGH, + REALTIME, +} macro void? Mutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {}); macro bool Mutex.is_initialized(mutex) => ((NativeMutex*)&mutex).is_initialized(); macro void? RecursiveMutex.init(&mutex) @maydiscard => NativeMutex.init((NativeMutex*)mutex, {.recursive}); -macro void? Mutex.destroy(&mutex) @maydiscard => NativeMutex.destroy((NativeMutex*)mutex); // Remove optional in 0.8.0 +macro void Mutex.destroy(&mutex) => NativeMutex.destroy((NativeMutex*)mutex); macro void? Mutex.lock(&mutex) @maydiscard => NativeMutex.lock((NativeMutex*)mutex); macro bool Mutex.try_lock(&mutex) => NativeMutex.try_lock((NativeMutex*)mutex); -macro void? Mutex.unlock(&mutex) @maydiscard => NativeMutex.unlock((NativeMutex*)mutex); // Remove optional in 0.8.0 +macro void Mutex.unlock(&mutex) => NativeMutex.unlock((NativeMutex*)mutex); macro void? TimedMutex.init(&mutex) => NativeTimedMutex.init((NativeTimedMutex*)mutex, {.timed}); macro void? TimedRecursiveMutex.init(&mutex) @maydiscard => NativeTimedMutex.init((NativeTimedMutex*)mutex, {.timed, .recursive}); -macro void? TimedMutex.destroy(&mutex) @maydiscard => NativeTimedMutex.destroy((NativeTimedMutex*)mutex); // Remove optional in 0.8.0 +macro void TimedMutex.destroy(&mutex) => NativeTimedMutex.destroy((NativeTimedMutex*)mutex); macro void? TimedMutex.lock(&mutex) @maydiscard => NativeTimedMutex.lock((NativeTimedMutex*)mutex); -macro void? TimedMutex.lock_timeout(&mutex, ulong ms) => NativeTimedMutex.lock_timeout((NativeTimedMutex*)mutex, ms); -macro bool TimedMutex.try_lock(&mutex) => NativeTimedMutex.try_lock((NativeTimedMutex*)mutex); -macro void? TimedMutex.unlock(&mutex) @maydiscard => NativeTimedMutex.unlock((NativeTimedMutex*)mutex); // Remove optional in 0.8.0 +macro void? TimedMutex.lock_timeout(&mutex, long ms) => NativeTimedMutex.lock_timeout((NativeTimedMutex*)mutex, ms); +macro bool TimedMutex.try_lock(&mutex) => NativeTimedMutex.try_lock((NativeTimedMutex*)mutex); +macro void TimedMutex.unlock(&mutex) => NativeTimedMutex.unlock((NativeTimedMutex*)mutex); + +struct ThreadSettings +{ + sz stack_size; + ThreadPriority priority; +} macro void fence(AtomicOrdering $ordering) @safemacro { @@ -97,10 +105,11 @@ macro void? ConditionVariable.wait_until(&cond, Mutex* mutex, Time time) Create and start a thread. @require thread_fn != null : "A non null thread function is required" + @return? thread::INIT_FAILED, thread::INVALID_THREAD_SETTINGS *> -macro void? Thread.create(&thread, ThreadFn thread_fn, void* arg) +macro void? Thread.create(&thread, ThreadFn thread_fn, void* arg, ThreadSettings settings = {}) { - return NativeThread.create(thread, thread_fn, arg); + return NativeThread.create(thread, thread_fn, arg, settings); } macro void? Thread.detach(thread) @maydiscard => NativeThread.detach(thread); @@ -110,16 +119,16 @@ macro void? Thread.detach(thread) @maydiscard => NativeThread.detach(thread); *> macro int? Thread.join(thread) @maydiscard => NativeThread.join(thread); macro bool Thread.equals(thread, Thread other) => NativeThread.equals(thread, other); - +macro bool Thread.set_priority(thread, ThreadPriority priority) => NativeThread.set_priority(&thread, priority); macro void OnceFlag.call(&flag, OnceFn func) => NativeOnceFlag.call_once((NativeOnceFlag*)flag, func); macro void yield() => os::native_thread_yield(); macro Thread current() => (Thread)os::native_thread_current(); macro void exit(int result) { - allocator::destroy_temp_allocators(); + allocators::destroy_temp_allocators(); os::native_thread_exit(result); } macro void? sleep(Duration d) @maydiscard => os::native_sleep_nano(d.to_nano()); -macro void? sleep_ms(ulong ms) @maydiscard => sleep(time::ms(ms)); +macro void? sleep_ms(long ms) @maydiscard => sleep(time::ms(ms)); macro void? sleep_ns(NanoDuration ns) @maydiscard => os::native_sleep_nano(ns); diff --git a/test/stdlib/src/threads/unbuffered_channel.c3 b/test/stdlib/src/threads/unbuffered_channel.c3 index 932c5aa..6de8810 100644 --- a/test/stdlib/src/threads/unbuffered_channel.c3 +++ b/test/stdlib/src/threads/unbuffered_channel.c3 @@ -1,27 +1,25 @@ module std::thread::channel ; -typedef UnbufferedChannel = void*; +typedef UnbufferedChannel = void; struct UnbufferedChannelImpl @private { Allocator allocator; Mutex mu; - Type buf; + Type val; bool closed; - - Mutex send_mu; - usz send_waiting; + bool has_value; + sz send_waiting; ConditionVariable send_cond; - Mutex read_mu; - usz read_waiting; + sz read_waiting; ConditionVariable read_cond; } -fn void? UnbufferedChannel.init(&self, Allocator allocator) +fn UnbufferedChannel*? create_unbuffered(Allocator allocator) { - UnbufferedChannelImpl* channel = allocator::alloc(allocator, UnbufferedChannelImpl); - defer catch (void)allocator::free(allocator, channel); + UnbufferedChannelImpl* channel = alloc::alloc(allocator, UnbufferedChannelImpl); + defer catch (void)alloc::free(allocator, channel); channel.allocator = allocator; channel.send_waiting = 0; @@ -29,98 +27,87 @@ fn void? UnbufferedChannel.init(&self, Allocator allocator) channel.mu.init()!; defer catch channel.mu.destroy(); - channel.send_mu.init()!; - defer catch channel.send_mu.destroy(); channel.send_cond.init()!; defer catch channel.send_cond.destroy(); - channel.read_mu.init()!; - defer catch channel.read_mu.destroy(); channel.read_cond.init()!; - *self = (UnbufferedChannel)channel; + return (UnbufferedChannel*)channel; } -fn void? UnbufferedChannel.destroy(&self) @maydiscard // Remove optional in 0.8.0 +fn void UnbufferedChannel.destroy(&self) { - UnbufferedChannelImpl* channel = (UnbufferedChannelImpl*)(*self); - + UnbufferedChannelImpl* channel = (UnbufferedChannelImpl*)(self); channel.mu.destroy(); - channel.send_mu.destroy(); channel.send_cond.destroy(); - channel.read_mu.destroy(); channel.read_cond.destroy(); - allocator::free(channel.allocator, channel); - - *self = null; + alloc::free(channel.allocator, channel); } -fn void? UnbufferedChannel.push(self, Type val) +fn void? UnbufferedChannel.push(&self, Type val) { UnbufferedChannelImpl* channel = (UnbufferedChannelImpl*)self; channel.mu.lock(); - defer catch channel.mu.unlock(); - channel.send_mu.lock(); - defer catch channel.send_mu.unlock(); + defer channel.mu.unlock(); - if (channel.closed) + // if channel is full -> wait + while (channel.has_value && !channel.closed) { - return thread::CHANNEL_CLOSED~; + channel.send_waiting++; + channel.send_cond.wait(&channel.mu); + channel.send_waiting--; } - // store value in the buffer - channel.buf = val; - // show that we are waiting for reader - channel.send_waiting++; + // check if channel is closed + if (channel.closed) return thread::CHANNEL_CLOSED~; - // if reader is already waiting for us -> awake him + // save value to buf + channel.val = val; + channel.has_value = true; + + // if someone is waiting -> awake him if (channel.read_waiting > 0) { channel.read_cond.signal(); } - - // wait until reader takes value from buffer - channel.send_cond.wait(&channel.mu); - - if (channel.closed) return thread::CHANNEL_CLOSED~; - - channel.mu.unlock(); - channel.send_mu.unlock(); } -fn Type? UnbufferedChannel.pop(self) +fn Type? UnbufferedChannel.pop(&self) { UnbufferedChannelImpl* channel = (UnbufferedChannelImpl*)self; channel.mu.lock(); - defer catch channel.mu.unlock(); - channel.read_mu.lock(); - defer catch channel.read_mu.unlock(); + defer channel.mu.unlock(); - // if no one is waiting, then there is nothing in the buffer - while (!channel.closed && channel.send_waiting == 0) + // if chan is empty -> wait for sender + while (!channel.has_value && !channel.closed) { channel.read_waiting++; channel.read_cond.wait(&channel.mu); channel.read_waiting--; } - if (channel.closed) return thread::CHANNEL_CLOSED~; + // check if chan is closed and empty + if (channel.closed && !channel.has_value) + { + return thread::CHANNEL_CLOSED~; + } - // take value from buffer - Type ret = channel.buf; + // read from buf + Type ret = channel.val; - // awake sender - channel.send_waiting--; - channel.send_cond.signal(); + channel.has_value = false; - channel.mu.unlock(); - channel.read_mu.unlock(); + // if someone is waiting -> awake him + if (channel.send_waiting > 0) + { + channel.send_cond.signal(); + } return ret; } -fn void? UnbufferedChannel.close(self) @maydiscard // Remove optional in 0.8.0 +fn void UnbufferedChannel.close(&self) { UnbufferedChannelImpl* channel = (UnbufferedChannelImpl*)self; diff --git a/test/stdlib/src/time/datetime.c3 b/test/stdlib/src/time/datetime.c3 index e3b6443..2c0d541 100644 --- a/test/stdlib/src/time/datetime.c3 +++ b/test/stdlib/src/time/datetime.c3 @@ -13,10 +13,10 @@ fn DateTime now() @require sec >= 0 && sec < 60 @require us >= 0 && us <= 999_999 *> -fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0) +fn DateTime at(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0) { DateTime dt @noinit; - dt.set_date(year, month, day, hour, min, sec, us) @inline; + dt.set(year, month, day, hour, min, sec, us) @inline; return dt; } @@ -28,9 +28,9 @@ fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0 @require us >= 0 && us <= 999_999 @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 *> -fn TzDateTime from_date_tz(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0, int gmt_offset = 0) +fn TzDateTime at_tz(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0, int gmt_offset = 0) { - return from_date(year, month, day, hour, min, sec, us).with_gmt_offset(gmt_offset); + return at(year, month, day, hour, min, sec, us).with_gmt_offset(gmt_offset); } fn TzDateTime DateTime.to_local(&self) @@ -44,9 +44,9 @@ fn TzDateTime DateTime.to_local(&self) dt.min = (char)tm.tm_min; dt.hour = (char)tm.tm_hour; dt.day = (char)tm.tm_mday; - dt.month = Month.from_ordinal(tm.tm_mon); + dt.month = Month::from_ordinal(tm.tm_mon); dt.year = tm.tm_year + 1900; - dt.weekday = !tm.tm_wday ? Weekday.SUNDAY : Weekday.from_ordinal(tm.tm_wday - 1); + dt.weekday = !tm.tm_wday ? Weekday.SUNDAY : Weekday::from_ordinal(tm.tm_wday - 1); dt.year_day = (ushort)tm.tm_yday; dt.time = self.time; $if $defined(tm.tm_gmtoff): @@ -120,11 +120,11 @@ fn bool TzDateTime.eq(self, TzDateTime other) @operator(==) @inline <* @require day >= 1 && day < 32 @require hour >= 0 && hour < 24 - @require min >= 0 && min <= 60 + @require min >= 0 && min < 60 @require sec >= 0 && sec < 60 @require us >= 0 && us <= 999_999 *> -fn void DateTime.set_date(&self, int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0) +fn void DateTime.set(&self, int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0) { Tm tm; tm.tm_sec = sec; @@ -147,9 +147,9 @@ fn void DateTime.set_time(&self, Time time) self.min = (char)tm.tm_min; self.hour = (char)tm.tm_hour; self.day = (char)tm.tm_mday; - self.month = Month.from_ordinal(tm.tm_mon); + self.month = Month::from_ordinal(tm.tm_mon); self.year = tm.tm_year + 1900; - self.weekday = !tm.tm_wday ? Weekday.SUNDAY : Weekday.from_ordinal(tm.tm_wday - 1); + self.weekday = !tm.tm_wday ? Weekday.SUNDAY : Weekday::from_ordinal(tm.tm_wday - 1); self.year_day = (ushort)tm.tm_yday; self.time = time; } @@ -165,7 +165,7 @@ fn DateTime DateTime.add_weeks(&self, int weeks) => from_time(self.time.add_week fn DateTime DateTime.add_years(&self, int years) { if (!years) return *self; - return from_date(self.year + years, self.month, self.day, self.hour, self.min, self.sec, self.usec); + return at(self.year + years, self.month, self.day, self.hour, self.min, self.sec, self.usec); } fn DateTime DateTime.add_months(&self, int months) @@ -190,7 +190,7 @@ fn DateTime DateTime.add_months(&self, int months) year += month / 12; month %= 12; } - return from_date(year, Month.from_ordinal(month), self.day, self.hour, self.min, self.sec, self.usec); + return at(year, Month::from_ordinal(month), self.day, self.hour, self.min, self.sec, self.usec); } diff --git a/test/stdlib/src/time/format.c3 b/test/stdlib/src/time/format.c3 index 51ee3a2..e2f14a8 100644 --- a/test/stdlib/src/time/format.c3 +++ b/test/stdlib/src/time/format.c3 @@ -1,5 +1,5 @@ module std::time::datetime @if(env::LIBC); - +import std::io; enum DateTimeFormat { @@ -20,47 +20,57 @@ enum DateTimeFormat TIMEONLY, // "15:04:05" } -fn String format(Allocator allocator, DateTimeFormat type, TzDateTime dt) +fn sz? to_format(Formatter* f, TzDateTime dt, DateTimeFormat type) => @pool() { switch (type) { case ANSIC: - return string::format(allocator, "%s %s %2d %02d:%02d:%02d %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year); + return f.printf("%s %s %2d %02d:%02d:%02d %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year); case UNIXDATE: - return string::format(allocator, "%s %s %2d %02d:%02d:%02d GMT %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year); + return f.printf("%s %s %2d %02d:%02d:%02d GMT %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year); case RUBYDATE: - return string::format(allocator, "%s %s %2d %02d:%02d:%02d %s %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset), dt.year); + return f.printf("%s %s %2d %02d:%02d:%02d %s %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset), dt.year); case RFC822: dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs - return string::format(allocator, "%02d %s %02d %02d:%02d GMT", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min); + return f.printf("%02d %s %02d %02d:%02d GMT", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min); case RFC822Z: - return string::format(allocator, "%02d %s %02d %02d:%02d %s", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, temp_numeric_tzsuffix(dt.gmt_offset)); + return f.printf("%02d %s %02d %02d:%02d %s", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, temp_numeric_tzsuffix(dt.gmt_offset)); case RFC850: dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs - return string::format(allocator, "%s, %02d-%s-%02d %02d:%02d:%02d GMT", dt.weekday.name, dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, dt.sec); + return f.printf("%s, %02d-%s-%02d %02d:%02d:%02d GMT", dt.weekday.name, dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, dt.sec); case RFC1123: dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs - return string::format(allocator, "%s, %02d %s %d %02d:%02d:%02d GMT", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec); + return f.printf("%s, %02d %s %d %02d:%02d:%02d GMT", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec); case RFC1123Z: - return string::format(allocator, "%s, %02d %s %d %02d:%02d:%02d %s", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset)); + return f.printf("%s, %02d %s %d %02d:%02d:%02d %s", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset)); case RFC3339: dt = dt.to_gmt_offset(0); - return string::format(allocator, "%04d-%02d-%02dT%02d:%02d:%02dZ", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec); + return f.printf("%04d-%02d-%02dT%02d:%02d:%02dZ", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec); case RFC3339Z: - return string::format(allocator, "%04d-%02d-%02dT%02d:%02d:%02d%s", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix_colon(dt.gmt_offset)); + return f.printf("%04d-%02d-%02dT%02d:%02d:%02d%s", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix_colon(dt.gmt_offset)); case RFC3339MS: dt = dt.to_gmt_offset(0); - return string::format(allocator, "%04d-%02d-%02dT%02d:%02d:%02d.%dZ", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec); + return f.printf("%04d-%02d-%02dT%02d:%02d:%02d.%dZ", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec); case RFC3339ZMS: - return string::format(allocator, "%04d-%02d-%02dT%02d:%02d:%02d.%d%s", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec, temp_numeric_tzsuffix_colon(dt.gmt_offset)); + return f.printf("%04d-%02d-%02dT%02d:%02d:%02d.%d%s", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec, temp_numeric_tzsuffix_colon(dt.gmt_offset)); case DATETIME: - return string::format(allocator, "%04d-%02d-%02d %02d:%02d:%02d", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec); + return f.printf("%04d-%02d-%02d %02d:%02d:%02d", dt.year, dt.month.ordinal + 1, dt.day, dt.hour, dt.min, dt.sec); case DATEONLY: - return string::format(allocator, "%04d-%02d-%02d", dt.year, dt.month.ordinal + 1, dt.day); + return f.printf("%04d-%02d-%02d", dt.year, dt.month.ordinal + 1, dt.day); case TIMEONLY: - return string::format(allocator, "%02d:%02d:%02d", dt.hour, dt.min, dt.sec); + return f.printf("%02d:%02d:%02d", dt.hour, dt.min, dt.sec); } } +fn String format(Allocator allocator, DateTimeFormat type, TzDateTime dt) +{ + DString s; + s.init(tmem, 40); + Formatter formatter; + formatter.init(&dstring::append_fn, &s); + to_format(&formatter, dt, type)!!; + if (allocator == tmem) return s.str_view(); + return s.copy_str(allocator); +} fn String tformat(DateTimeFormat dt_format, TzDateTime dt) => format(tmem, dt_format, dt); @@ -69,6 +79,16 @@ fn String TzDateTime.format(self, Allocator allocator, DateTimeFormat dt_format) // .with_gmt_offset(0) instead of .to_local() is used to avoid surprises when user is formatting to a representation without a timezone fn String DateTime.format(self, Allocator allocator, DateTimeFormat dt_format) => format(allocator, dt_format, self.with_gmt_offset(0)); +fn sz? DateTime.to_format(&self, Formatter* f) @dynamic +{ + return to_format(f, self.with_gmt_offset(0), DATETIME); +} + +fn sz? TzDateTime.to_format(&self, Formatter* f) @dynamic +{ + return to_format(f, *self, RFC3339MS); +} + <* Returns the timezone offset in the format of "+HHMM" or "-HHMM" @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 diff --git a/test/stdlib/src/time/time.c3 b/test/stdlib/src/time/time.c3 index 8d1b528..fe5ac10 100644 --- a/test/stdlib/src/time/time.c3 +++ b/test/stdlib/src/time/time.c3 @@ -1,13 +1,13 @@ module std::time; import std::io, std::time::os; -typedef Time @structlike = long; -typedef Duration @structlike = long; -typedef Clock @structlike = ulong; -typedef NanoDuration (Printable) @structlike = long; +typedef Time = long; +typedef Duration = long; +typedef Clock = ulong; +typedef NanoDuration (Printable) = long; -const Time FAR_FUTURE = (Time)long.max; -const Time FAR_PAST = (Time)long.min; +const Time FAR_FUTURE = (Time)long::max; +const Time FAR_PAST = (Time)long::min; const NanoDuration NANO_DURATION_ZERO = (NanoDuration)0; const Duration US = (Duration)1; @@ -19,7 +19,7 @@ const Duration DAY = (Duration)24 * HOUR; const Duration WEEK = (Duration)7 * DAY; const Duration MONTH = (Duration)30 * DAY; const Duration YEAR = (Duration)36525 * DAY / 100; -const Duration FOREVER = (Duration)long.max; +const Duration FOREVER = (Duration)long::max; const Duration DURATION_ZERO = (Duration)0; fn Duration us(long l) @inline => l * US; @@ -29,7 +29,7 @@ fn Duration min(long l) @inline => l * MIN; fn Duration hour(long l) @inline => l * HOUR; fn Duration from_float(double s) @inline => (Duration)(s * (double)SEC); -struct DateTime +struct DateTime (Printable) { int usec; char sec; @@ -43,7 +43,7 @@ struct DateTime Time time; } -struct TzDateTime +struct TzDateTime (Printable) { inline DateTime date_time; int gmt_offset; @@ -103,7 +103,7 @@ fn double Time.to_seconds(time) => (long)time / (double)SEC; fn Duration Time.diff_us(time, Time other) @operator(-) => (Duration)((long)time - (long)other); fn double Time.diff_sec(time, Time other) => (long)time.diff_us(other) / (double)SEC; fn double Time.diff_min(time, Time other) => (long)time.diff_us(other) / (double)MIN; -fn double Time.diff_hour(time, Time other) => (long)time.diff_us(other) / (double)HOUR; +fn double Time.diff_hours(time, Time other) => (long)time.diff_us(other) / (double)HOUR; fn double Time.diff_days(time, Time other) => (long)time.diff_us(other) / (double)DAY; fn double Time.diff_weeks(time, Time other) => (long)time.diff_us(other) / (double)WEEK; @@ -118,7 +118,7 @@ fn String NanoDuration.unit_str(&self, Allocator allocator, bool use_short_units { static String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" }; static String[] short_units = { "ns", "us", "ms", "s" }; // using Mu here causes spacing oddities, just using 'us' - usz units_idx = 0; + sz units_idx = 0; double reduced_value = (double)*self; for (; reduced_value > 1_000 && units_idx < units.len - 1; reduced_value /= 1_000, units_idx++); @@ -137,7 +137,7 @@ fn String NanoDuration.tunit_str(&self, bool use_short_units = true) return self.unit_str(tmem, use_short_units); } -fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic +fn sz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic { NanoDuration nd = *self; if (nd == NANO_DURATION_ZERO)