ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: 9ebb77f907d30a3516e643a07b64d8c822bb85d2
Parent: 34c5d20511da0480bd83b52eb2d74d26c33d3aa0
Author: Randy Palamar
Date:   Mon, 22 Jun 2026 12:01:21 -0600

build: allow Expand inside Table definition

This allows stuff like:

@Enumeration FooBar
{
	Nil
	@Expand(FooTable) `$(name)`
	@Expand(BarTable) `$(name)`
}

or similar for other table entities.

Diffstat:
Mbuild.c | 1172++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mutil.c | 7++++---
2 files changed, 632 insertions(+), 547 deletions(-)

diff --git a/build.c b/build.c @@ -2170,6 +2170,392 @@ meta_entity_first_child_of_kind(MetaContext *ctx, MetaEntity *e, MetaEntityKind } function void +meta_expansion_string_split(str8 string, str8 *left, str8 *inner, str8 *remainder, MetaLocation loc) +{ + b32 found = 0; + for (u8 *s = string.data, *e = s + string.length; (s + 1) != e; s++) { + u32 val = (u32)'$' << 8u | (u32)'('; + u32 test = (u32)s[0] << 8u | s[1]; + if (test == val) { + if (left) { + left->data = string.data; + left->length = s - string.data; + } + + u8 *start = s + 2; + while (s != e && *s != ')') s++; + if (s == e) { + meta_compiler_error_message(loc, "unterminated expansion in raw string:\n %.*s\n", + (i32)string.length, string.data); + fprintf(stderr, " %.*s^\n", (i32)(start - string.data), ""); + meta_error(); + } + + if (inner) { + inner->data = start; + inner->length = s - start; + } + + if (remainder) { + remainder->data = s + 1; + remainder->length = string.length - (remainder->data - string.data); + } + found = 1; + break; + } + } + if (!found) { + if (left) *left = string; + if (inner) *inner = (str8){0}; + if (remainder) *remainder = (str8){0}; + } +} + +function MetaExpansionPart * +meta_push_expansion_part(MetaContext *ctx, Arena *arena, MetaExpansionPartList *parts, + MetaExpansionPartKind kind, str8 string, MetaEntity *table, MetaLocation loc) +{ + MetaExpansionPart *result = da_push(arena, parts); + + result->kind = kind; + switch (kind) { + case MetaExpansionPartKind_Alignment: + case MetaExpansionPartKind_Conditional: + {}break; + + case MetaExpansionPartKind_EvalKind: + case MetaExpansionPartKind_EvalKindCount: + case MetaExpansionPartKind_Reference: + { + assert(meta_entity_kind_is_table[table->kind]); + MetaTable *t = &table->table; + + da_count index = meta_lookup_string_slow(t->fields, t->field_count, s8_from_str8(string)); + result->strings = t->entries[index]; + if (index < 0) { + /* TODO(rnp): fix this location to point directly at the field in the string */ + s8 table_name = ctx->entity_names.data[da_index(table, &ctx->entities)]; + meta_compiler_error(loc, "table \"%.*s\" does not contain member: %.*s\n", + (i32)table_name.len, table_name.data, (i32)string.length, string.data); + } + }break; + + case MetaExpansionPartKind_String:{ result->string = s8_from_str8(string); }break; + InvalidDefaultCase; + } + return result; +} + +#define META_EXPANSION_TOKEN_LIST \ + X('|', Alignment) \ + X('%', TypeEval) \ + X('#', TypeEvalElements) \ + X('"', Quote) \ + X('-', Dash) \ + X('>', GreaterThan) \ + X('<', LessThan) \ + +typedef enum { + MetaExpansionToken_EOF, + MetaExpansionToken_Identifier, + MetaExpansionToken_Number, + MetaExpansionToken_String, + #define X(__1, kind, ...) MetaExpansionToken_## kind, + META_EXPANSION_TOKEN_LIST + #undef X + MetaExpansionToken_Count, +} MetaExpansionToken; + +read_only global s8 meta_expansion_token_strings[] = { + s8_comp("EOF"), + s8_comp("Indentifier"), + s8_comp("Number"), + s8_comp("String"), + #define X(s, kind, ...) s8_comp(#s), + META_EXPANSION_TOKEN_LIST + #undef X +}; + +typedef struct { + str8 s; + union { + i64 number; + str8 string; + }; + str8 save; + MetaLocation loc; +} MetaExpansionParser; + +#define meta_expansion_save(v) (v)->save = (v)->s +#define meta_expansion_restore(v) swap((v)->s, (v)->save) +#define meta_expansion_commit(v) meta_expansion_restore(v) + +#define meta_expansion_expected(loc, e, g) \ + meta_compiler_error(loc, "invalid expansion string: expected %.*s after %.*s\n", \ + (i32)meta_expansion_token_strings[e].len, meta_expansion_token_strings[e].data, \ + (i32)meta_expansion_token_strings[g].len, meta_expansion_token_strings[g].data) + +function str8 +meta_expansion_extract_string(MetaExpansionParser *p) +{ + str8 result = {.data = p->s.data}; + for (; result.length < p->s.length; result.length++) { + b32 done = 0; + switch (p->s.data[result.length]) { + #define X(t, ...) case t: + META_EXPANSION_TOKEN_LIST + #undef X + case ' ': + {done = 1;}break; + default:{}break; + } + if (done) break; + } + p->s.data += result.length; + p->s.length -= result.length; + return result; +} + +function MetaExpansionToken +meta_expansion_token(MetaExpansionParser *p) +{ + MetaExpansionToken result = MetaExpansionToken_EOF; + meta_expansion_save(p); + if (p->s.length > 0) { + b32 chop = 1; + switch (p->s.data[0]) { + #define X(t, kind, ...) case t:{ result = MetaExpansionToken_## kind; }break; + META_EXPANSION_TOKEN_LIST + #undef X + default:{ + chop = 0; + if (BETWEEN(p->s.data[0], '0', '9')) result = MetaExpansionToken_Number; + else result = MetaExpansionToken_Identifier; + }break; + } + if (chop) { + str8_chop(&p->s, 1); + p->s = str8_trim(p->s); + } + + switch (result) { + case MetaExpansionToken_Number:{ + NumberConversion integer = integer_from_str8(p->s); + if (integer.result != NumberConversionResult_Success) { + /* TODO(rnp): point at start */ + meta_compiler_error(p->loc, "invalid integer in expansion string\n"); + } + p->number = integer.S64; + p->s = integer.unparsed; + }break; + case MetaExpansionToken_Identifier:{ p->string = meta_expansion_extract_string(p); }break; + default:{}break; + } + p->s = str8_trim(p->s); + } + return result; +} + +function MetaExpansionPart * +meta_expansion_start_conditional(MetaContext *ctx, Arena *arena, MetaExpansionPartList *ops, + MetaExpansionParser *p, MetaExpansionToken token, b32 negate) +{ + MetaExpansionPart *result = meta_push_expansion_part(ctx, arena, ops, MetaExpansionPartKind_Conditional, + str8(""), 0, p->loc); + switch (token) { + case MetaExpansionToken_Number:{ + result->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Number; + result->conditional.lhs.number = negate ? -p->number : p->number; + }break; + default:{}break; + } + return result; +} + +function void +meta_expansion_end_conditional(MetaExpansionPart *ep, MetaExpansionParser *p, MetaExpansionToken token, b32 negate) +{ + if (ep->conditional.rhs.kind != MetaExpansionConditionalArgumentKind_Invalid) { + meta_compiler_error(p->loc, "invalid expansion conditional: duplicate right hand expression: '%.*s'\n", + (i32)p->save.length, p->save.data); + } + switch (token) { + case MetaExpansionToken_Number:{ + ep->conditional.rhs.kind = MetaExpansionConditionalArgumentKind_Number; + ep->conditional.rhs.number = negate ? -p->number : p->number; + }break; + default:{}break; + } +} + +function MetaExpansionPartList +meta_generate_expansion_set(MetaContext *ctx, Arena *arena, str8 expansion_string, MetaEntity *table, MetaLocation loc) +{ + MetaExpansionPartList result = {0}; + str8 left = {0}, inner, remainder = expansion_string; + do { + meta_expansion_string_split(remainder, &left, &inner, &remainder, loc); + if (left.length) meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, left, table, loc); + if (inner.length) { + MetaExpansionParser p[1] = {{.s = inner, .loc = loc}}; + + MetaExpansionPart *test_part = 0; + b32 count_test_parts = 0; + + for (MetaExpansionToken token = meta_expansion_token(p); + token != MetaExpansionToken_EOF; + token = meta_expansion_token(p)) + { + if (count_test_parts) test_part->conditional.instruction_skip++; + switch (token) { + case MetaExpansionToken_Alignment:{ + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Alignment, p->s, table, loc); + }break; + + case MetaExpansionToken_Identifier:{ + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Reference, p->string, table, loc); + }break; + + case MetaExpansionToken_TypeEval: + case MetaExpansionToken_TypeEvalElements: + { + if (meta_expansion_token(p) != MetaExpansionToken_Identifier) { + loc.column += (u32)(p->save.data - expansion_string.data); + meta_expansion_expected(loc, MetaExpansionToken_Identifier, token); + } + MetaExpansionPartKind kind = token == MetaExpansionToken_TypeEval ? + MetaExpansionPartKind_EvalKind : + MetaExpansionPartKind_EvalKindCount; + meta_push_expansion_part(ctx, arena, &result, kind, p->string, table, loc); + }break; + + case MetaExpansionToken_Quote:{ + u8 *point = p->s.data; + str8 string = meta_expansion_extract_string(p); + token = meta_expansion_token(p); + if (token != MetaExpansionToken_Quote) { + loc.column += (u32)(point - expansion_string.data); + /* TODO(rnp): point at start */ + meta_compiler_error(loc, "unterminated string in expansion\n"); + } + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, string, table, loc); + }break; + + case MetaExpansionToken_Dash:{ + token = meta_expansion_token(p); + switch (token) { + case MetaExpansionToken_GreaterThan:{ + if (!test_part) goto error; + if (test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid || + test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid) + { + b32 lhs = test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid; + b32 rhs = test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid; + if (lhs && rhs) + meta_compiler_error(loc, "expansion string test terminated without arguments\n"); + meta_compiler_error(loc, "expansion string test terminated without %s argument\n", + lhs? "left" : "right"); + } + count_test_parts = 1; + }break; + case MetaExpansionToken_Number:{ + if (test_part) meta_expansion_end_conditional(test_part, p, token, 1); + else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 1); + }break; + default:{ goto error; }break; + } + }break; + + case MetaExpansionToken_Number:{ + if (test_part) meta_expansion_end_conditional(test_part, p, token, 0); + else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); + }break; + + case MetaExpansionToken_GreaterThan: + case MetaExpansionToken_LessThan: + { + if (test_part && test_part->conditional.op != MetaExpansionOperation_Invalid) goto error; + if (!test_part) { + if (result.count == 0) { + meta_compiler_error(p->loc, "invalid expansion conditional: missing left hand side\n"); + } + + s8 *strings = result.data[result.count - 1].strings; + MetaExpansionPartKind last_kind = result.data[result.count - 1].kind; + if (last_kind != MetaExpansionPartKind_EvalKindCount && + last_kind != MetaExpansionPartKind_Reference) + { + meta_compiler_error(p->loc, "invalid expansion conditional: left hand side not numeric\n"); + } + result.count--; + test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); + if (last_kind == MetaExpansionPartKind_EvalKindCount) { + test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Evaluation; + } else { + test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Reference; + } + test_part->conditional.lhs.strings = strings; + } + test_part->conditional.op = token == MetaExpansionToken_LessThan ? + MetaExpansionOperation_LessThan : + MetaExpansionOperation_GreaterThan; + }break; + + error: + default: + { + meta_compiler_error(loc, "invalid nested %.*s in expansion string\n", + (i32)meta_expansion_token_strings[token].len, + meta_expansion_token_strings[token].data); + }break; + } + } + } + } while (remainder.length); + return result; +} + +function da_count +meta_expand_table_entity_id(MetaContext *ctx, MetaEntry *e) +{ + assert(e->kind == MetaEntryKind_Expand); + + /* TODO(rnp): for now this requires that the @Table came first */ + meta_entry_argument_expected(e, s8("table_name")); + s8 table_name = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; + + da_count result = meta_lookup_string_slow(ctx->entity_names.data, ctx->entity_names.count, table_name); + + if (result < 0) meta_entry_error(e, "undefined table %.*s\n", (i32)table_name.len, table_name.data); + + MetaEntity *table = ctx->entities.data + result; + if (!meta_entity_kind_is_table[table->kind]) { + s8 old_kind = meta_entity_kind_names[table->kind]; + s8 wanted_kind = meta_entity_kind_names[MetaEntityKind_Table]; + meta_entry_error(e, "%.*s previously defined as %.*s but should be %.*s\n", + (i32)table_name.len, table_name.data, + (i32)old_kind.len, old_kind.data, + (i32)wanted_kind.len, wanted_kind.data); + } + + return result; +} + +function s8 +meta_expand_parts_to_s8_at_index(MetaContext *ctx, u64 table_index, MetaExpansionPartList parts) +{ + Stream sb = arena_stream(*ctx->arena); + for EachIndex((u64)parts.count, part) { + MetaExpansionPart *p = parts.data + part; + u32 index = 0; + if (p->kind == MetaExpansionPartKind_Reference) index = table_index; + stream_append_s8(&sb, p->strings[index]); + } + s8 result = arena_stream_commit(ctx->arena, &sb); + return result; +} + +function void meta_pack_table_begin(MetaEntry *e, MetaTable *t) { switch (e->kind) { @@ -2237,568 +2623,290 @@ meta_pack_table_entity(MetaContext *ctx, MetaEntry *e, i64 entry_count, s8 name, e->kind == MetaEntryKind_Union; MetaEntryScope scope = meta_entry_extract_scope(e, entry_count); - if (scope.consumed > 1) { - for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) { - if (row->kind != MetaEntryKind_Array && row->kind != MetaEntryKind_String) - meta_entry_nesting_error(row, e->kind); - - MetaEntryArgument entries = {.count = 1}; - if (row->kind == MetaEntryKind_Array) - entries.count = meta_entry_argument_expect(row, 0, MetaEntryArgumentKind_Array).count; - - if (structure && entries.count != 2 && entries.count != 3) { - meta_compiler_error(row->location, "incorrect field count for @%s entry got: %zu expected: " - "[name type (elements)]\n", meta_entry_kind_strings[e->kind], - (size_t)entries.count); - } else if (!structure && entries.count != t->field_count) { - meta_compiler_error_message(row->location, "incorrect field count for @%s entry got: %zu expected: %u\n", - meta_entry_kind_strings[e->kind], (size_t)entries.count, t->field_count); - fprintf(stderr, " fields: ["); - for (u64 i = 0; i < t->field_count; i++) { - if (i != 0) fprintf(stderr, " "); - fprintf(stderr, "%.*s", (i32)t->fields[i].len, t->fields[i].data); - } - fprintf(stderr, "]\n"); - meta_error(); - } - - t->entry_count++; - } - - t->entries = push_array(ctx->arena, s8 *, t->field_count); - for (u32 field = 0; field < t->field_count; field++) - t->entries[field] = push_array(ctx->arena, s8, t->entry_count); - - u32 row_index = 0; - for (MetaEntry *row = scope.start; row != scope.one_past_last; row++, row_index++) { - s8 *fs = &row->name; - if (row->arguments) - fs = row->arguments->strings; - - for (u32 field = 0; field < t->field_count; field++) - t->entries[field][row_index] = fs[field]; - - // NOTE(rnp): if we are filling out a struct the array element count is optional - // and defaults to 1. fill this out here for uniformity elsewhere in the code - if (structure && row->arguments->count == 2) - t->entries[2][row_index] = s8("1"); - } - } - - MetaEntity *entity = meta_entity(ctx, entity_id); - entity->table = table; - - switch (e->kind) { - case MetaEntryKind_Bake: - case MetaEntryKind_PushConstants: - case MetaEntryKind_Struct: - case MetaEntryKind_Union: - case MetaEntryKind_Enumeration: - case MetaEntryKind_Table: - {}break; - - InvalidDefaultCase; - } - - return scope.consumed; -} - -function i64 -meta_pack_shader_common(MetaContext *ctx, MetaEntityID shader_id, MetaEntry *e, i64 entry_count, MetaEntityID group_entity_id) -{ - assert(ctx->entities.data[shader_id.value].kind == MetaEntityKind_Shader); - i64 result = 0; - - switch(e->kind) { - - case MetaEntryKind_Bake:{ - e->name = push_s8_from_parts(ctx->arena, s8(""), ctx->entity_names.data[shader_id.value], s8("BakeParameters")); - result = meta_pack_table_entity(ctx, e, entry_count, e->name, shader_id); - }break; - - case MetaEntryKind_PushConstants:{ - e->name = push_s8_from_parts(ctx->arena, s8(""), ctx->entity_names.data[shader_id.value], s8("PushConstants")); - result = meta_pack_table_entity(ctx, e, entry_count, e->name, shader_id); - goto reference; - }break; - - case MetaEntryKind_ShaderAlias:{ - MetaEntityID alias_id = meta_intern_entity(ctx, e->name, MetaEntityKind_Shader, group_entity_id, - e->location, 0); - meta_entity(ctx, alias_id)->shader.kind = MetaShaderKind_Alias; - meta_entity(ctx, alias_id)->shader.alias_parent_id = shader_id; - }break; - - case MetaEntryKind_Enumeration: - case MetaEntryKind_Constant: - case MetaEntryKind_Struct: - reference: - { - meta_entry_argument_expected(e); - // TODO(rnp): MetaIDList.data should be of type MetaEntityID - MetaEntityID ref_id = meta_entity_reference(ctx, e->name, e->location); - meta_intern_id(ctx, &meta_entity(ctx, shader_id)->shader.entity_reference_ids, ref_id.value); - }break; - - default:{ meta_entry_nesting_error(e, MetaEntryKind_Shader); }break; - } - - return result; -} - -function i64 -meta_pack_render_shader(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID group_entity_id) -{ - assert(entries[0].kind == MetaEntryKind_RenderShader); - - MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_Shader, - group_entity_id, entries->location, 0); - meta_entity(ctx, entity_id)->shader.kind = MetaShaderKind_Render; - - meta_entry_argument_expected(entries); - - MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); - if (scope.consumed > 1) { - for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { - switch (e->kind) { - - case MetaEntryKind_VertexShader:{ - if (meta_entity(ctx, entity_id)->shader.files[0].len) - meta_entry_error(e, "primitive shader file redefined\n"); - meta_entity(ctx, entity_id)->shader.files[0] = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; - meta_entity(ctx, entity_id)->shader.render.kind = MetaShaderPrimitiveKind_Vertex; - }break; - - case MetaEntryKind_FragmentShader:{ - if (meta_entity(ctx, entity_id)->shader.files[1].len) - meta_entry_error(e, "fragment shader file redefined\n"); - meta_entity(ctx, entity_id)->shader.files[1] = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; - }break; - - default:{ - e += meta_pack_shader_common(ctx, entity_id, e, scope.one_past_last - e, group_entity_id); - }break; - } - } - } - return scope.consumed; -} - -function i64 -meta_pack_compute_shader(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID group_entity_id) -{ - assert(entries[0].kind == MetaEntryKind_Shader); - - MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_Shader, group_entity_id, - entries->location, 0); - meta_entity(ctx, entity_id)->shader.kind = MetaShaderKind_Compute; - - if (entries->argument_count > 1) { - meta_entry_argument_expected(entries, s8("[file_name]")); - } else if (entries->argument_count == 1) { - s8 shader_file = meta_entry_argument_expect(entries, 0, MetaEntryArgumentKind_String).string; - meta_entity(ctx, entity_id)->shader.files[0] = shader_file; - } - - MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); - if (scope.consumed > 1) { - for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) - e += meta_pack_shader_common(ctx, entity_id, e, scope.one_past_last - e, group_entity_id); - } else { - assert(scope.consumed == 1); - // TODO(rnp): some functions (@Expand) expect no scope and that the next entry - // is treated as in scope; here we do not want that behaviour. - scope.consumed = 0; - } - return scope.consumed; -} - -function i64 -meta_pack_shader_group(MetaContext *ctx, MetaEntry *entries, i64 entry_count) -{ - assert(entries->kind == MetaEntryKind_ShaderGroup); - - MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_ShaderGroup, - meta_root_entity_id(ctx), entries->location, 0); - - MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); - if (scope.consumed > 1) { - for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { - switch (e->kind) { - case MetaEntryKind_RenderShader:{ - e += meta_pack_render_shader(ctx, e, scope.one_past_last - e, entity_id); - }break; - case MetaEntryKind_Shader:{ - e += meta_pack_compute_shader(ctx, e, scope.one_past_last - e, entity_id); - }break; - default:{meta_entry_nesting_error(e, MetaEntryKind_ShaderGroup);}break; - } - } - } - return scope.consumed; -} + if (scope.consumed > 1) { + Arena scratch = ctx->scratch; -function i64 -meta_pack_references(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID parent, s8 scope_name, s8 prefix) -{ - MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); - for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { - switch (e->kind) { - case MetaEntryKind_Struct: - case MetaEntryKind_Union: - { - meta_entity_reference_reference(ctx, e->name, scope_name, e->location, parent, prefix); - }break; - default:{meta_entry_nesting_error(e, entries->kind);}break; + // NOTE(rnp): count expands + i64 expand_count = 0; + for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) + if (row->kind == MetaEntryKind_Expand) + expand_count++; + + // NOTE(rnp): extract expand tables + da_count *table_ids = 0; + i64 table_id_index = 0; + if (expand_count > 0) { + table_ids = push_array(&ctx->scratch, da_count, expand_count); + for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) + if (row->kind == MetaEntryKind_Expand) + table_ids[table_id_index++] = meta_expand_table_entity_id(ctx, row); } - } - return scope.consumed; -} -function void -meta_expansion_string_split(str8 string, str8 *left, str8 *inner, str8 *remainder, MetaLocation loc) -{ - b32 found = 0; - for (u8 *s = string.data, *e = s + string.length; (s + 1) != e; s++) { - u32 val = (u32)'$' << 8u | (u32)'('; - u32 test = (u32)s[0] << 8u | s[1]; - if (test == val) { - if (left) { - left->data = string.data; - left->length = s - string.data; + table_id_index = 0; + for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) { + if (row->kind != MetaEntryKind_Array && + row->kind != MetaEntryKind_Expand && + row->kind != MetaEntryKind_String) + { + meta_entry_nesting_error(row, e->kind); } - u8 *start = s + 2; - while (s != e && *s != ')') s++; - if (s == e) { - meta_compiler_error_message(loc, "unterminated expansion in raw string:\n %.*s\n", - (i32)string.length, string.data); - fprintf(stderr, " %.*s^\n", (i32)(start - string.data), ""); - meta_error(); + MetaEntryArgument entries = {.count = 1}; + + if (row->kind == MetaEntryKind_Expand) { + if (row + 1 == scope.one_past_last || ( + row[1].kind != MetaEntryKind_Array && + row[1].kind != MetaEntryKind_String)) + { + meta_entry_nesting_error(row + 1, row->kind); + } + + if (row[1].kind == MetaEntryKind_Array) + entries.count = meta_entry_argument_expect(row + 1, 0, MetaEntryArgumentKind_Array).count; } - if (inner) { - inner->data = start; - inner->length = s - start; + if (row->kind == MetaEntryKind_Array) + entries.count = meta_entry_argument_expect(row, 0, MetaEntryArgumentKind_Array).count; + + if (structure && entries.count != 2 && entries.count != 3) { + meta_compiler_error(row->location, "incorrect field count for @%s entry got: %zu expected: " + "[name type (elements)]\n", meta_entry_kind_strings[e->kind], + (size_t)entries.count); + } else if (!structure && entries.count != t->field_count) { + meta_compiler_error_message(row->location, "incorrect field count for @%s entry got: %zu expected: %u\n", + meta_entry_kind_strings[e->kind], (size_t)entries.count, t->field_count); + fprintf(stderr, " fields: ["); + for (u64 i = 0; i < t->field_count; i++) { + if (i != 0) fprintf(stderr, " "); + fprintf(stderr, "%.*s", (i32)t->fields[i].len, t->fields[i].data); + } + fprintf(stderr, "]\n"); + meta_error(); } - if (remainder) { - remainder->data = s + 1; - remainder->length = string.length - (remainder->data - string.data); + if (row->kind == MetaEntryKind_Expand) { + t->entry_count += ctx->entities.data[table_ids[table_id_index++]].table.entry_count; + // NOTE(rnp): skip expand argument + row++; + } else { + t->entry_count++; } - found = 1; - break; } - } - if (!found) { - if (left) *left = string; - if (inner) *inner = (str8){0}; - if (remainder) *remainder = (str8){0}; - } -} - -function MetaExpansionPart * -meta_push_expansion_part(MetaContext *ctx, Arena *arena, MetaExpansionPartList *parts, - MetaExpansionPartKind kind, str8 string, MetaEntity *table, MetaLocation loc) -{ - MetaExpansionPart *result = da_push(arena, parts); - - result->kind = kind; - switch (kind) { - case MetaExpansionPartKind_Alignment: - case MetaExpansionPartKind_Conditional: - {}break; - case MetaExpansionPartKind_EvalKind: - case MetaExpansionPartKind_EvalKindCount: - case MetaExpansionPartKind_Reference: - { - assert(meta_entity_kind_is_table[table->kind]); - MetaTable *t = &table->table; + t->entries = push_array(ctx->arena, s8 *, t->field_count); + for (u32 field = 0; field < t->field_count; field++) + t->entries[field] = push_array(ctx->arena, s8, t->entry_count); - da_count index = meta_lookup_string_slow(t->fields, t->field_count, s8_from_str8(string)); - result->strings = t->entries[index]; - if (index < 0) { - /* TODO(rnp): fix this location to point directly at the field in the string */ - s8 table_name = ctx->entity_names.data[da_index(table, &ctx->entities)]; - meta_compiler_error(loc, "table \"%.*s\" does not contain member: %.*s\n", - (i32)table_name.len, table_name.data, (i32)string.length, string.data); - } - }break; + u32 row_index = 0; + table_id_index = 0; + for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) { + u64 argument_count = row->arguments ? row->arguments->count : 1; + if (row->kind == MetaEntryKind_Expand) { + row++; + argument_count = row->arguments ? row->arguments->count : 1; - case MetaExpansionPartKind_String:{ result->string = s8_from_str8(string); }break; - InvalidDefaultCase; - } - return result; -} + MetaEntity *table = ctx->entities.data + table_ids[table_id_index++]; -#define META_EXPANSION_TOKEN_LIST \ - X('|', Alignment) \ - X('%', TypeEval) \ - X('#', TypeEvalElements) \ - X('"', Quote) \ - X('-', Dash) \ - X('>', GreaterThan) \ - X('<', LessThan) \ + u32 working_row_index = row_index; + for EachIndex(argument_count, it) { + working_row_index = row_index; + str8 expand = str8_from_s8(row->arguments ? row->arguments->strings[it] : row->name); + MetaExpansionPartList parts = meta_generate_expansion_set(ctx, &ctx->scratch, expand, table, row->location); + for EachIndex(table->table.entry_count, entry_index) + t->entries[it][working_row_index++] = meta_expand_parts_to_s8_at_index(ctx, entry_index, parts); + } -typedef enum { - MetaExpansionToken_EOF, - MetaExpansionToken_Identifier, - MetaExpansionToken_Number, - MetaExpansionToken_String, - #define X(__1, kind, ...) MetaExpansionToken_## kind, - META_EXPANSION_TOKEN_LIST - #undef X - MetaExpansionToken_Count, -} MetaExpansionToken; + for (; row_index < working_row_index; row_index++) + if (structure && argument_count == 2) + t->entries[2][row_index] = s8("1"); -read_only global s8 meta_expansion_token_strings[] = { - s8_comp("EOF"), - s8_comp("Indentifier"), - s8_comp("Number"), - s8_comp("String"), - #define X(s, kind, ...) s8_comp(#s), - META_EXPANSION_TOKEN_LIST - #undef X -}; + } else { + s8 *fs = &row->name; + if (row->arguments) + fs = row->arguments->strings; + + for (u32 field = 0; field < t->field_count; field++) + t->entries[field][row_index] = fs[field]; + + // NOTE(rnp): if we are filling out a struct the array element count is optional + // and defaults to 1. fill this out here for uniformity elsewhere in the code + if (structure && argument_count == 2) + t->entries[2][row_index] = s8("1"); + row_index++; + } + } -typedef struct { - str8 s; - union { - i64 number; - str8 string; - }; - str8 save; - MetaLocation loc; -} MetaExpansionParser; + ctx->scratch = scratch; + } -#define meta_expansion_save(v) (v)->save = (v)->s -#define meta_expansion_restore(v) swap((v)->s, (v)->save) -#define meta_expansion_commit(v) meta_expansion_restore(v) + MetaEntity *entity = meta_entity(ctx, entity_id); + entity->table = table; -#define meta_expansion_expected(loc, e, g) \ - meta_compiler_error(loc, "invalid expansion string: expected %.*s after %.*s\n", \ - (i32)meta_expansion_token_strings[e].len, meta_expansion_token_strings[e].data, \ - (i32)meta_expansion_token_strings[g].len, meta_expansion_token_strings[g].data) + switch (e->kind) { + case MetaEntryKind_Bake: + case MetaEntryKind_PushConstants: + case MetaEntryKind_Struct: + case MetaEntryKind_Union: + case MetaEntryKind_Enumeration: + case MetaEntryKind_Table: + {}break; -function str8 -meta_expansion_extract_string(MetaExpansionParser *p) -{ - str8 result = {.data = p->s.data}; - for (; result.length < p->s.length; result.length++) { - b32 done = 0; - switch (p->s.data[result.length]) { - #define X(t, ...) case t: - META_EXPANSION_TOKEN_LIST - #undef X - case ' ': - {done = 1;}break; - default:{}break; - } - if (done) break; + InvalidDefaultCase; } - p->s.data += result.length; - p->s.length -= result.length; - return result; + + return scope.consumed; } -function MetaExpansionToken -meta_expansion_token(MetaExpansionParser *p) +function i64 +meta_pack_shader_common(MetaContext *ctx, MetaEntityID shader_id, MetaEntry *e, i64 entry_count, MetaEntityID group_entity_id) { - MetaExpansionToken result = MetaExpansionToken_EOF; - meta_expansion_save(p); - if (p->s.length > 0) { - b32 chop = 1; - switch (p->s.data[0]) { - #define X(t, kind, ...) case t:{ result = MetaExpansionToken_## kind; }break; - META_EXPANSION_TOKEN_LIST - #undef X - default:{ - chop = 0; - if (BETWEEN(p->s.data[0], '0', '9')) result = MetaExpansionToken_Number; - else result = MetaExpansionToken_Identifier; - }break; - } - if (chop) { - str8_chop(&p->s, 1); - p->s = str8_trim(p->s); - } + assert(ctx->entities.data[shader_id.value].kind == MetaEntityKind_Shader); + i64 result = 0; - switch (result) { - case MetaExpansionToken_Number:{ - NumberConversion integer = integer_from_str8(p->s); - if (integer.result != NumberConversionResult_Success) { - /* TODO(rnp): point at start */ - meta_compiler_error(p->loc, "invalid integer in expansion string\n"); - } - p->number = integer.S64; - p->s = integer.unparsed; - }break; - case MetaExpansionToken_Identifier:{ p->string = meta_expansion_extract_string(p); }break; - default:{}break; - } - p->s = str8_trim(p->s); - } - return result; -} + switch(e->kind) { -function MetaExpansionPart * -meta_expansion_start_conditional(MetaContext *ctx, Arena *arena, MetaExpansionPartList *ops, - MetaExpansionParser *p, MetaExpansionToken token, b32 negate) -{ - MetaExpansionPart *result = meta_push_expansion_part(ctx, arena, ops, MetaExpansionPartKind_Conditional, - str8(""), 0, p->loc); - switch (token) { - case MetaExpansionToken_Number:{ - result->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Number; - result->conditional.lhs.number = negate ? -p->number : p->number; + case MetaEntryKind_Bake:{ + e->name = push_s8_from_parts(ctx->arena, s8(""), ctx->entity_names.data[shader_id.value], s8("BakeParameters")); + result = meta_pack_table_entity(ctx, e, entry_count, e->name, shader_id); + }break; + + case MetaEntryKind_PushConstants:{ + e->name = push_s8_from_parts(ctx->arena, s8(""), ctx->entity_names.data[shader_id.value], s8("PushConstants")); + result = meta_pack_table_entity(ctx, e, entry_count, e->name, shader_id); + goto reference; + }break; + + case MetaEntryKind_ShaderAlias:{ + MetaEntityID alias_id = meta_intern_entity(ctx, e->name, MetaEntityKind_Shader, group_entity_id, + e->location, 0); + meta_entity(ctx, alias_id)->shader.kind = MetaShaderKind_Alias; + meta_entity(ctx, alias_id)->shader.alias_parent_id = shader_id; }break; - default:{}break; - } - return result; -} -function void -meta_expansion_end_conditional(MetaExpansionPart *ep, MetaExpansionParser *p, MetaExpansionToken token, b32 negate) -{ - if (ep->conditional.rhs.kind != MetaExpansionConditionalArgumentKind_Invalid) { - meta_compiler_error(p->loc, "invalid expansion conditional: duplicate right hand expression: '%.*s'\n", - (i32)p->save.length, p->save.data); - } - switch (token) { - case MetaExpansionToken_Number:{ - ep->conditional.rhs.kind = MetaExpansionConditionalArgumentKind_Number; - ep->conditional.rhs.number = negate ? -p->number : p->number; + case MetaEntryKind_Enumeration: + case MetaEntryKind_Constant: + case MetaEntryKind_Struct: + reference: + { + meta_entry_argument_expected(e); + // TODO(rnp): MetaIDList.data should be of type MetaEntityID + MetaEntityID ref_id = meta_entity_reference(ctx, e->name, e->location); + meta_intern_id(ctx, &meta_entity(ctx, shader_id)->shader.entity_reference_ids, ref_id.value); }break; - default:{}break; + + default:{ meta_entry_nesting_error(e, MetaEntryKind_Shader); }break; } + + return result; } -function MetaExpansionPartList -meta_generate_expansion_set(MetaContext *ctx, Arena *arena, str8 expansion_string, MetaEntity *table, MetaLocation loc) +function i64 +meta_pack_render_shader(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID group_entity_id) { - MetaExpansionPartList result = {0}; - str8 left = {0}, inner, remainder = expansion_string; - do { - meta_expansion_string_split(remainder, &left, &inner, &remainder, loc); - if (left.length) meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, left, table, loc); - if (inner.length) { - MetaExpansionParser p[1] = {{.s = inner, .loc = loc}}; + assert(entries[0].kind == MetaEntryKind_RenderShader); - MetaExpansionPart *test_part = 0; - b32 count_test_parts = 0; + MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_Shader, + group_entity_id, entries->location, 0); + meta_entity(ctx, entity_id)->shader.kind = MetaShaderKind_Render; - for (MetaExpansionToken token = meta_expansion_token(p); - token != MetaExpansionToken_EOF; - token = meta_expansion_token(p)) - { - if (count_test_parts) test_part->conditional.instruction_skip++; - switch (token) { - case MetaExpansionToken_Alignment:{ - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Alignment, p->s, table, loc); - }break; + meta_entry_argument_expected(entries); - case MetaExpansionToken_Identifier:{ - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Reference, p->string, table, loc); - }break; + MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); + if (scope.consumed > 1) { + for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { + switch (e->kind) { - case MetaExpansionToken_TypeEval: - case MetaExpansionToken_TypeEvalElements: - { - if (meta_expansion_token(p) != MetaExpansionToken_Identifier) { - loc.column += (u32)(p->save.data - expansion_string.data); - meta_expansion_expected(loc, MetaExpansionToken_Identifier, token); - } - MetaExpansionPartKind kind = token == MetaExpansionToken_TypeEval ? - MetaExpansionPartKind_EvalKind : - MetaExpansionPartKind_EvalKindCount; - meta_push_expansion_part(ctx, arena, &result, kind, p->string, table, loc); - }break; + case MetaEntryKind_VertexShader:{ + if (meta_entity(ctx, entity_id)->shader.files[0].len) + meta_entry_error(e, "primitive shader file redefined\n"); + meta_entity(ctx, entity_id)->shader.files[0] = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; + meta_entity(ctx, entity_id)->shader.render.kind = MetaShaderPrimitiveKind_Vertex; + }break; - case MetaExpansionToken_Quote:{ - u8 *point = p->s.data; - str8 string = meta_expansion_extract_string(p); - token = meta_expansion_token(p); - if (token != MetaExpansionToken_Quote) { - loc.column += (u32)(point - expansion_string.data); - /* TODO(rnp): point at start */ - meta_compiler_error(loc, "unterminated string in expansion\n"); - } - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, string, table, loc); - }break; + case MetaEntryKind_FragmentShader:{ + if (meta_entity(ctx, entity_id)->shader.files[1].len) + meta_entry_error(e, "fragment shader file redefined\n"); + meta_entity(ctx, entity_id)->shader.files[1] = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; + }break; - case MetaExpansionToken_Dash:{ - token = meta_expansion_token(p); - switch (token) { - case MetaExpansionToken_GreaterThan:{ - if (!test_part) goto error; - if (test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid || - test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid) - { - b32 lhs = test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid; - b32 rhs = test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid; - if (lhs && rhs) - meta_compiler_error(loc, "expansion string test terminated without arguments\n"); - meta_compiler_error(loc, "expansion string test terminated without %s argument\n", - lhs? "left" : "right"); - } - count_test_parts = 1; - }break; - case MetaExpansionToken_Number:{ - if (test_part) meta_expansion_end_conditional(test_part, p, token, 1); - else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 1); - }break; - default:{ goto error; }break; - } - }break; + default:{ + e += meta_pack_shader_common(ctx, entity_id, e, scope.one_past_last - e, group_entity_id); + }break; + } + } + } + return scope.consumed; +} - case MetaExpansionToken_Number:{ - if (test_part) meta_expansion_end_conditional(test_part, p, token, 0); - else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); - }break; +function i64 +meta_pack_compute_shader(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID group_entity_id) +{ + assert(entries[0].kind == MetaEntryKind_Shader); - case MetaExpansionToken_GreaterThan: - case MetaExpansionToken_LessThan: - { - if (test_part && test_part->conditional.op != MetaExpansionOperation_Invalid) goto error; - if (!test_part) { - if (result.count == 0) { - meta_compiler_error(p->loc, "invalid expansion conditional: missing left hand side\n"); - } + MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_Shader, group_entity_id, + entries->location, 0); + meta_entity(ctx, entity_id)->shader.kind = MetaShaderKind_Compute; - s8 *strings = result.data[result.count - 1].strings; - MetaExpansionPartKind last_kind = result.data[result.count - 1].kind; - if (last_kind != MetaExpansionPartKind_EvalKindCount && - last_kind != MetaExpansionPartKind_Reference) - { - meta_compiler_error(p->loc, "invalid expansion conditional: left hand side not numeric\n"); - } - result.count--; - test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); - if (last_kind == MetaExpansionPartKind_EvalKindCount) { - test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Evaluation; - } else { - test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Reference; - } - test_part->conditional.lhs.strings = strings; - } - test_part->conditional.op = token == MetaExpansionToken_LessThan ? - MetaExpansionOperation_LessThan : - MetaExpansionOperation_GreaterThan; - }break; + if (entries->argument_count > 1) { + meta_entry_argument_expected(entries, s8("[file_name]")); + } else if (entries->argument_count == 1) { + s8 shader_file = meta_entry_argument_expect(entries, 0, MetaEntryArgumentKind_String).string; + meta_entity(ctx, entity_id)->shader.files[0] = shader_file; + } - error: - default: - { - meta_compiler_error(loc, "invalid nested %.*s in expansion string\n", - (i32)meta_expansion_token_strings[token].len, - meta_expansion_token_strings[token].data); - }break; - } + MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); + if (scope.consumed > 1) { + for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) + e += meta_pack_shader_common(ctx, entity_id, e, scope.one_past_last - e, group_entity_id); + } else { + assert(scope.consumed == 1); + // TODO(rnp): some functions (@Expand) expect no scope and that the next entry + // is treated as in scope; here we do not want that behaviour. + scope.consumed = 0; + } + return scope.consumed; +} + +function i64 +meta_pack_shader_group(MetaContext *ctx, MetaEntry *entries, i64 entry_count) +{ + assert(entries->kind == MetaEntryKind_ShaderGroup); + + MetaEntityID entity_id = meta_intern_entity(ctx, entries->name, MetaEntityKind_ShaderGroup, + meta_root_entity_id(ctx), entries->location, 0); + + MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); + if (scope.consumed > 1) { + for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { + switch (e->kind) { + case MetaEntryKind_RenderShader:{ + e += meta_pack_render_shader(ctx, e, scope.one_past_last - e, entity_id); + }break; + case MetaEntryKind_Shader:{ + e += meta_pack_compute_shader(ctx, e, scope.one_past_last - e, entity_id); + }break; + default:{meta_entry_nesting_error(e, MetaEntryKind_ShaderGroup);}break; } } - } while (remainder.length); - return result; + } + return scope.consumed; +} + +function i64 +meta_pack_references(MetaContext *ctx, MetaEntry *entries, i64 entry_count, MetaEntityID parent, s8 scope_name, s8 prefix) +{ + MetaEntryScope scope = meta_entry_extract_scope(entries, entry_count); + for (MetaEntry *e = scope.start; e < scope.one_past_last; e++) { + switch (e->kind) { + case MetaEntryKind_Struct: + case MetaEntryKind_Union: + { + meta_entity_reference_reference(ctx, e->name, scope_name, e->location, parent, prefix); + }break; + default:{meta_entry_nesting_error(e, entries->kind);}break; + } + } + return scope.consumed; } function s8 * @@ -2806,16 +2914,8 @@ meta_expand_to_s8_array(MetaContext *ctx, Arena scratch, s8 expand, MetaEntity * { MetaExpansionPartList parts = meta_generate_expansion_set(ctx, &scratch, str8_from_s8(expand), table, location); s8 *result = push_array(ctx->arena, s8, table->table.entry_count); - for EachIndex(table->table.entry_count, expansion) { - Stream sb = arena_stream(*ctx->arena); - for EachIndex((u64)parts.count, part) { - MetaExpansionPart *p = parts.data + part; - u32 index = 0; - if (p->kind == MetaExpansionPartKind_Reference) index = expansion; - stream_append_s8(&sb, p->strings[index]); - } - result[expansion] = arena_stream_commit(ctx->arena, &sb); - } + for EachIndex(table->table.entry_count, expansion) + result[expansion] = meta_expand_parts_to_s8_at_index(ctx, expansion, parts); return result; } @@ -2824,24 +2924,8 @@ meta_expand(MetaContext *ctx, Arena scratch, MetaEntry *e, iz entry_count, MetaE { assert(e->kind == MetaEntryKind_Expand); - /* TODO(rnp): for now this requires that the @Table came first */ - meta_entry_argument_expected(e, s8("table_name")); - s8 table_name = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; - - MetaEntity *table = ctx->entities.data + meta_lookup_string_slow(ctx->entity_names.data, - ctx->entity_names.count, - table_name); - - if (table < ctx->entities.data) - meta_entry_error(e, "undefined table %.*s\n", (i32)table_name.len, table_name.data); - if (!meta_entity_kind_is_table[table->kind]) { - s8 old_kind = meta_entity_kind_names[table->kind]; - s8 wanted_kind = meta_entity_kind_names[MetaEntityKind_Table]; - meta_entry_error(e, "%.*s previously defined as %.*s but should be %.*s\n", - (i32)table_name.len, table_name.data, - (i32)old_kind.len, old_kind.data, - (i32)wanted_kind.len, wanted_kind.data); - } + MetaEntity *table = ctx->entities.data + meta_expand_table_entity_id(ctx, e); + s8 table_name = ctx->entity_names.data[da_index(table, &ctx->entities)]; MetaEntryScope scope = meta_entry_extract_scope(e, entry_count); for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) { diff --git a/util.c b/util.c @@ -81,10 +81,11 @@ memory_copy_non_temporal(void *restrict dest, void *restrict src, u64 n) } function void -memory_move(u8 *dest, u8 *src, u64 n) +memory_move(void *dest, void *src, u64 n) { - if (dest < src) memory_copy(dest, src, n); - else while (n) { n--; dest[n] = src[n]; } + u8 *d = dest, *s = src; + if (d < s) memory_copy(d, s, n); + else while (n) { n--; d[n] = s[n]; } } function void *