ONYX_INTRINSIC_MEMORY_SIZE, ONYX_INTRINSIC_MEMORY_GROW,
ONYX_INTRINSIC_MEMORY_COPY, ONYX_INTRINSIC_MEMORY_FILL,
+ ONYX_INTRINSIC_MEMORY_EQUAL,
ONYX_INTRINSIC_INITIALIZE,
// be imported as. If NULL, the last token of
// the package path is used. The following
// imports the core package as C.
- //
+ //
// use core { C :: package }
//
OnyxToken *qualified_package_name;
typedef struct ContextCaches {
bh_imap implicit_cast_to_bool_cache;
-} ContextCaches;
+} ContextCaches;
typedef struct DefinedVariable {
char *key;
WI_I32X4_TRUNC_SAT_F32X4_U = SIMD_INSTR_MASK | 249,
WI_F32X4_CONVERT_I32X4_S = SIMD_INSTR_MASK | 250,
WI_F32X4_CONVERT_I32X4_U = SIMD_INSTR_MASK | 251,
-
-
+
+
WI_MEMORY_INIT = EXT_INSTR_MASK | 0x08,
WI_MEMORY_COPY = EXT_INSTR_MASK | 0x0a,
WI_MEMORY_FILL = EXT_INSTR_MASK | 0x0b,
bh_imap elem_map;
bh_arr(DeferredStmt) deferred_stmts;
- bh_arr(AllocatedSpace) local_allocations;
+ bh_arr(AllocatedSpace) local_allocations;
bh_arr(PatchInfo) stack_leave_patches;
bh_arr(DatumPatchInfo) data_patches;
void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer);
void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file);
-#ifdef ONYX_RUNTIME_LIBRARY
+#ifdef ONYX_RUNTIME_LIBRARY
void onyx_run_initialize(b32 debug_enabled);
b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]);
#endif
static IntrinsicMap builtin_intrinsics[] = {
{ "unreachable", ONYX_INTRINSIC_UNREACHABLE },
- { "memory_size", ONYX_INTRINSIC_MEMORY_SIZE },
- { "memory_grow", ONYX_INTRINSIC_MEMORY_GROW },
- { "memory_copy", ONYX_INTRINSIC_MEMORY_COPY },
- { "memory_fill", ONYX_INTRINSIC_MEMORY_FILL },
+ { "memory_size", ONYX_INTRINSIC_MEMORY_SIZE },
+ { "memory_grow", ONYX_INTRINSIC_MEMORY_GROW },
+ { "memory_copy", ONYX_INTRINSIC_MEMORY_COPY },
+ { "memory_fill", ONYX_INTRINSIC_MEMORY_FILL },
+ { "memory_equal", ONYX_INTRINSIC_MEMORY_EQUAL },
{ "__initialize", ONYX_INTRINSIC_INITIALIZE },
fori (i, 0, Binary_Op_Count) {
operator_overloads[i] = NULL;
- bh_arr_new(global_heap_allocator, operator_overloads[i], 4);
+ bh_arr_new(global_heap_allocator, operator_overloads[i], 4);
}
fori (i, 0, Unary_Op_Count) {
unary_operator_overloads[i] = NULL;
- bh_arr_new(global_heap_allocator, unary_operator_overloads[i], 4);
+ bh_arr_new(global_heap_allocator, unary_operator_overloads[i], 4);
}
IntrinsicMap* intrinsic = &builtin_intrinsics[0];
void introduce_build_options(bh_allocator a) {
Package* p = package_lookup_or_create("runtime", context.global_scope, a, context.global_scope->created_at);
-
+
// HACK creating this for later
package_lookup_or_create("runtime.vars", p->scope, a, context.global_scope->created_at);
if (parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0) {
bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) fc);
-
+
} else {
ENTITY_SUBMIT(fc);
}
export_name->token = parser->curr - 1;
export_name->func = (AstFunction *) parse_factor(parser);
export_name->type_node = builtin_string_type;
-
+
retval = (AstTyped *) export_name;
break;
}
}
method_call->right = (AstTyped *) parse_function_call(parser, (AstTyped *) method);
-
+
retval = (AstTyped *) method_call;
break;
}
case Binary_Op_Divide: return 8;
case Binary_Op_Modulus: return 9;
-
+
case Binary_Op_Coalesce: return 11;
default: return -1;
b32 is_pointer = 0;
if (consume_token_if_next(parser, '&') || consume_token_if_next(parser, '^'))
is_pointer = 1;
-
+
OnyxToken *capture_symbol = expect_token(parser, Token_Type_Symbol);
AstLocal *capture = make_local(parser->allocator, capture_symbol, NULL);
-
+
sc_node->capture = capture;
sc_node->capture_is_by_pointer = is_pointer;
}
expect_token(parser, '{');
-
+
parser->current_scope = scope_symbols_in_structures_should_be_bound_to;
b32 member_is_used = 0;
// #match
// something :: (....) {
// }
- //
+ //
// This will make converting something to a overloaded
// function easier and require less copying by the programmer.
if (next_tokens_are(parser, 2, ':', ':')) {
inject->documentation = parser->last_documentation_token;
parser->last_documentation_token = NULL;
}
-
+
ENTITY_SUBMIT(inject);
return;
}
return;
}
- DebugContext *ctx = mod->debug_context;
+ DebugContext *ctx = mod->debug_context;
assert(ctx && ctx->last_token);
// Sanity check
repeat_previous = 1;
}
}
-
+
if (repeat_previous) {
// Output / increment REP instruction
if (ctx->last_op_was_rep) {
EMIT_FUNC(struct_literal_into_contiguous_memory, AstStructLiteral* sl, u64 base_ptr_local, u32 offset);
EMIT_FUNC(wasm_copy, OnyxToken *token);
EMIT_FUNC(wasm_fill, OnyxToken *token);
+EMIT_FUNC(wasm_memory_equal, OnyxToken *token);
EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* block_token);
EMIT_FUNC_NO_ARGS(leave_structured_block);
WIL(for_node->token, WI_LOCAL_GET, step_local);
WI(for_node->token, WI_I32_ADD);
WIL(for_node->token, WI_LOCAL_SET, iter_local);
-
+
if (for_node->has_first) {
WIL(for_node->token, WI_I32_CONST, 0);
WIL(for_node->token, WI_LOCAL_SET, for_node->first_local);
WIL(NULL, WI_LOCAL_GET, union_capture_idx);
WIL(NULL, WI_PTR_CONST, union_expr_type->Union.alignment);
WI(NULL, WI_PTR_ADD);
-
+
WIL(NULL, WI_I32_CONST, type_size_of(sc->capture->type));
emit_wasm_copy(mod, &code, NULL);
}
emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Type_Index], reserve_size + 4);
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-
+
WIL(call_token, WI_LOCAL_GET, stack_top_store_local);
WIL(call_token, WI_I32_CONST, reserve_size);
WI(call_token, WI_I32_ADD);
} else {
emit_expression(mod, &code, call->callee);
-
+
u64 global_closure_base_idx = bh_imap_get(&mod->index_map, (u64) &builtin_closure_base);
WI(NULL, WI_DROP);
WIL(NULL, WI_GLOBAL_SET, global_closure_base_idx);
} else {
first_arg->value = (AstTyped *) tmp_local;
}
-
+
//
// Actually emit the function call.
emit_call(mod, &code, call_node);
switch (call->intrinsic) {
case ONYX_INTRINSIC_UNREACHABLE: WI(call->token, WI_UNREACHABLE); break;
-
+
case ONYX_INTRINSIC_MEMORY_SIZE: WID(call->token, WI_MEMORY_SIZE, 0x00); break;
case ONYX_INTRINSIC_MEMORY_GROW: WID(call->token, WI_MEMORY_GROW, 0x00); break;
case ONYX_INTRINSIC_MEMORY_COPY:
case ONYX_INTRINSIC_MEMORY_FILL:
emit_wasm_fill(mod, &code, call->token);
break;
+ case ONYX_INTRINSIC_MEMORY_EQUAL:
+ emit_wasm_memory_equal(mod, &code, call->token);
+ break;
case ONYX_INTRINSIC_INITIALIZE: {
Type* type_to_initialize = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem;
*pcode = code;
}
+EMIT_FUNC(wasm_memory_equal, OnyxToken *token) {
+ bh_arr(WasmInstruction) code = *pcode;
+ emit_intrinsic_memory_equal(mod, &code);
+ *pcode = code;
+}
+
EMIT_FUNC(values_into_contiguous_memory, u64 base_ptr_local, Type *type, u32 offset, i32 value_count, AstTyped **values) {
bh_arr(WasmInstruction) code = *pcode;
u64 capture_block_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
WIL(NULL, WI_LOCAL_TEE, capture_block_ptr);
-
+
// Populate the block
bh_arr_each(AstCaptureLocal *, capture, func->captures->captures) {
WIL(NULL, WI_LOCAL_GET, capture_block_ptr);
emit_store_instruction(mod, &code, (*capture)->type, (*capture)->offset);
}
-
+
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
WIL(NULL, WI_I32_CONST, func->captures->total_size_in_bytes);
} else {
WIL(NULL, WI_I32_CONST, type_alignment_of(field->expr->type));
}
-
+
WI(NULL, WI_I32_ADD);
WIL(NULL, WI_I32_CONST, type_size_of(field->type->Union.variants_ordered[1]->type));
if (fd == builtin_initialize_data_segments && !mod->doing_linking) {
// This is a large hack, but is necessary.
// This particular function (__initialize_data_segments) should not be generated
- // until the module is in its linking phase. This is because we have to wait
+ // until the module is in its linking phase. This is because we have to wait
// until we have reached the linking phase to know every data element that is
// and will be present. Without this, the generating data segments in the bodies
// of the functions is not possible.
u64 localidx = 0;
bh_arr_each(AstParam, param, fd->params) {
switch (type_get_param_pass(param->local->type)) {
- case Param_Pass_By_Multiple_Values:
+ case Param_Pass_By_Multiple_Values:
debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type);
bh_imap_put(&mod->local_map, (u64) param->local, localidx | LOCAL_IS_WASM);
localidx += type_structlike_mem_count(param->local->type);
bh_free(global_heap_allocator, strdata);
return;
}
-
+
// :ProperLinking
// The length used here is one greater than the string length, because
// we DO want to include the null-terminator in the outputted string.
static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum) {
datum->offset_ = 0;
- datum->id = NEXT_DATA_ID(mod);
+ datum->id = NEXT_DATA_ID(mod);
bh_arr_push(mod->data, *datum);
return datum->id;
}
if (parent_file == NULL) parent_file = ".";
char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator);
-
+
OnyxToken *filename_token = fc->filename_expr->token;
token_toggle_end(filename_token);
assert(builtin_link_options_type);
Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type);
-
+
AstStructLiteral *input = (AstStructLiteral *) node;
StructMember smem;
// :32BitPointers
EMIT_FUNC_NO_ARGS(intrinsic_memory_copy) {
bh_arr(WasmInstruction) code = *pcode;
-
+
// The stack should look like this:
// <count>
// <source>
// <dest>
-
+
u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
u64 source_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
-
+
WIL(NULL, WI_LOCAL_SET, count_local);
WIL(NULL, WI_LOCAL_SET, source_local);
WIL(NULL, WI_LOCAL_SET, dest_local);
-
+
// count is greater than 0
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 0);
WI(NULL, WI_I32_GT_S);
-
+
WID(NULL, WI_IF_START, 0x40);
WID(NULL, WI_LOOP_START, 0x40);
-
+
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 1);
WI(NULL, WI_I32_SUB);
WIL(NULL, WI_LOCAL_SET, count_local);
-
+
WIL(NULL, WI_LOCAL_GET, dest_local);
WIL(NULL, WI_LOCAL_GET, count_local);
WI(NULL, WI_PTR_ADD);
-
+
WIL(NULL, WI_LOCAL_GET, source_local);
WIL(NULL, WI_LOCAL_GET, count_local);
WI(NULL, WI_PTR_ADD);
-
+
WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 }));
-
+
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 0);
WI(NULL, WI_I32_GT_S);
WID(NULL, WI_COND_JUMP, 0x00);
-
+
WI(NULL, WI_LOOP_END);
WI(NULL, WI_IF_END);
-
+
local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-
+
*pcode = code;
}
EMIT_FUNC_NO_ARGS(intrinsic_memory_fill) {
bh_arr(WasmInstruction) code = *pcode;
-
+
// The stack should look like this:
// <count>
// <byte>
// <dest>
-
+
u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
u64 byte_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
-
+
WIL(NULL, WI_LOCAL_SET, count_local);
WIL(NULL, WI_LOCAL_SET, byte_local);
WIL(NULL, WI_LOCAL_SET, dest_local);
-
+
// count is greater than 0
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 0);
WI(NULL, WI_I32_GT_S);
-
+
WID(NULL, WI_IF_START, 0x40);
WID(NULL, WI_LOOP_START, 0x40);
-
+
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 1);
WI(NULL, WI_I32_SUB);
WIL(NULL, WI_LOCAL_SET, count_local);
-
+
WIL(NULL, WI_LOCAL_GET, dest_local);
WIL(NULL, WI_LOCAL_GET, count_local);
WI(NULL, WI_PTR_ADD);
-
+
WIL(NULL, WI_LOCAL_GET, byte_local);
WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 }));
-
+
WIL(NULL, WI_LOCAL_GET, count_local);
WID(NULL, WI_I32_CONST, 0);
WI(NULL, WI_I32_GT_S);
WID(NULL, WI_COND_JUMP, 0x00);
-
+
WI(NULL, WI_LOOP_END);
WI(NULL, WI_IF_END);
-
+
local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-
+
*pcode = code;
}
+// IMPROVE: This is a stripped down/unoptimized version of memcmp that only checks equality
+// between two blocks of memory (and doesn't return -1/1). Returns 1 if the blocks of memory are equal,
+// and 0 otherwise.
+// NOTE: This implementation is unsafe and assumes both blocks of memory are at least size_in_bytes long.
+EMIT_FUNC_NO_ARGS(intrinsic_memory_equal) {
+ bh_arr(WasmInstruction) code = *pcode;
+
+ // The stack should look like this:
+ // <size in bytes>
+ // <ptr>
+ // <ptr>
+
+ u64 size_in_bytes = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+ u64 b_addr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
+ u64 a_addr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
+
+ WIL(NULL, WI_LOCAL_SET, size_in_bytes);
+ WIL(NULL, WI_LOCAL_SET, b_addr);
+ WIL(NULL, WI_LOCAL_SET, a_addr);
+
+ u64 memory_is_equal = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+ WID(NULL, WI_I32_CONST, 1); // set to 1 so size_in_bytes == 0 will return true
+ WIL(NULL, WI_LOCAL_SET, memory_is_equal);
+
+ // if size_in_bytes > 0
+ WIL(NULL, WI_LOCAL_GET, size_in_bytes);
+ WID(NULL, WI_I32_CONST, 0);
+ WI (NULL, WI_I32_GT_S);
+ WID(NULL, WI_IF_START, 0x40);
+ u64 loop_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+ WIL(NULL, WI_I32_CONST, 0);
+ WIL(NULL, WI_LOCAL_SET, loop_idx);
+
+ WID(NULL, WI_LOOP_START, 0x40);
+ // if loop_idx >= size_in_bytes, break
+ WIL(NULL, WI_LOCAL_GET, loop_idx);
+ WIL(NULL, WI_LOCAL_GET, size_in_bytes);
+ WI (NULL, WI_I32_GE_S);
+ WID(NULL, WI_COND_JUMP, 0x01);
+
+ // a = *(a_addr + loop_idx)
+ WIL(NULL, WI_LOCAL_GET, a_addr);
+ WIL(NULL, WI_LOCAL_GET, loop_idx);
+ WI (NULL, WI_PTR_ADD);
+ WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
+
+ // b = *(b_addr + loop_idx)
+ WIL(NULL, WI_LOCAL_GET, b_addr);
+ WIL(NULL, WI_LOCAL_GET, loop_idx);
+ WI (NULL, WI_PTR_ADD);
+ WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
+
+ WI (NULL, WI_I32_EQ);
+ WIL(NULL, WI_LOCAL_SET, memory_is_equal);
+
+ // if bytes are not equal, break
+ WIL(NULL, WI_LOCAL_GET, memory_is_equal);
+ WI (NULL, WI_I32_EQZ);
+ WID(NULL, WI_COND_JUMP, 0x01);
+
+ // loop_idx += 1
+ WIL(NULL, WI_LOCAL_GET, loop_idx);
+ WIL(NULL, WI_I32_CONST, 1);
+ WI (NULL, WI_I32_ADD);
+ WIL(NULL, WI_LOCAL_SET, loop_idx);
+
+ WID(NULL, WI_JUMP, 0x00);
+ WI(NULL, WI_LOOP_END);
+ WI(NULL, WI_IF_END);
+
+ WIL(NULL, WI_LOCAL_GET, memory_is_equal);
+
+ local_raw_free(mod->local_alloc, WASM_TYPE_PTR); // a_addr
+ local_raw_free(mod->local_alloc, WASM_TYPE_PTR); // b_addr
+ local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // size_in_bytes
+ local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // memory_is_equal
+ local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // loop_idx
+
+ *pcode = code;
+}
EMIT_FUNC(initialize_type, Type* type, OnyxToken* where) {
bh_arr(WasmInstruction) code = *pcode;
emit_expression(mod, &code, *smem->initial_value);
emit_store_instruction(mod, &code, smem->type, smem->offset);
}
-
+
local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
break;
}
use core.intrinsics.wasm {
memory_size, memory_grow,
memory_copy, memory_fill,
+ memory_equal,
}
use core {memory, math}
heap_free :: (ptr: rawptr) {
#if Enable_Debug do assert(ptr != null, "Trying to free a null pointer.");
-
+
hb_ptr := cast(&heap_freed_block) (cast(uintptr) ptr - sizeof heap_allocated_block);
//
hb_ptr.size |= Allocated_Flag;
new_ptr := heap_alloc(new_size_, align);
#if runtime.Multi_Threading_Enabled do sync.mutex_lock(&heap_mutex);
-
+
memory_copy(new_ptr, ptr, old_size - sizeof heap_block);
heap_free(ptr);
return new_ptr;
`null_proc` is a special function that breaks the normal rules of type
checking. `null_proc`, or any procedure marked with `#null`, is assignable
to any function type, regardless of if the types match. For example,
-
+
f: (i32) -> i32 = null_proc;
-
+
Even though `null_proc` is a `() -> void` function, it bypasses that check
and gets assigned to `f`. If f is called, there will be a runtime exception.
This is by design.
#doc """
Represents a generic "iterator" or "generator" of a specific
type. Can be used in a for-loop natively.
-
+
`data` is used for contextual information and is passed to
the `next`, `close`, and `remove` procedures.
-
+
`next` is used to extract the next value out of the iterator.
It returns the next value, and a continuation flag. If the
flag is false, the value should be ignored and iteration should
stop.
-
+
`close` should called when the iterator has ended. This is
done automatically in for-loops, and in the `core.iter` library.
In for-loops, `close` is called no matter which way the for-loop
exits (`break`, `return`, etc). Using this rule, iterator can
be used to create "resources" that automatically close when you
are done with them.
-
+
`remove` is used to tell the iterator to remove the last value
returned from some underlying data store. Invoked automatically
using the `#remove` directive in a for-loop.
// But when stack_first is true, it is:
// reserved | stack | static-data | heap
stack_first := false;
-
+
// The size, in bytes of the stack.
stack_size := 16 * 65536; // 16 pages * 65536 bytes per page = 1 MiB stack
#doc """
Special type used to represent a package at runtime.
For example,
-
+
x: package_id = package main
-
+
Currently, there is not much you can do with this; it is
only used by the runtime.info library if you want to filter
tags based on which package they are coming from.
#operator == macro (p1, p2: package_id) => cast(u32) p1 == cast(u32) p2;
#operator != macro (p1, p2: package_id) => cast(u32) p1 != cast(u32) p2;
-
+#operator == macro (l, r: [$N]$T) => core.intrinsics.wasm.memory_equal(cast(rawptr)l, cast(rawptr)r, N * sizeof T);
+#operator != macro (l, r: [$N]$T) => !(l == r);
//
// DEPRECATED THINGS
memory_grow :: (val: u32) -> i32 #intrinsic ---
memory_copy :: (dst: rawptr, src: rawptr, count: i32) -> void #intrinsic ---
memory_fill :: (dst: rawptr, byte: u8, count: i32) -> void #intrinsic ---
+memory_equal :: (a: rawptr, b: rawptr, count: i32) -> bool #intrinsic --- // Isn't an actual wasm intrinsic right now, but will be if the proposal is accepted.
clz_i32 :: (val: i32) -> i32 #intrinsic ---
ctz_i32 :: (val: i32) -> i32 #intrinsic ---