fixes
This commit is contained in:
@@ -769,6 +769,12 @@ END;
|
||||
ip3 := @out3.inner;
|
||||
print("ptr-nested-field: {} {} {}\n", ip3.a, ip3.b, ip3.c);
|
||||
|
||||
// Store to many-pointer field must not corrupt adjacent memory
|
||||
MpHolder :: struct { items: [*]s64; sentinel: s64; }
|
||||
mph := MpHolder.{ items = xx 0, sentinel = 42 };
|
||||
mph.items = xx 0;
|
||||
print("mp-store-sentinel: {}\n", mph.sentinel);
|
||||
|
||||
// --- Vectors ---
|
||||
vc := vec3(1, 3, 2);
|
||||
print("vec-construct: {}\n", vc);
|
||||
@@ -3165,6 +3171,10 @@ END;
|
||||
print("wasm 64-bit??\n");
|
||||
}
|
||||
}
|
||||
|
||||
// POINTER_SIZE in regular (non-inline) if expression
|
||||
ps := if POINTER_SIZE == 8 then "8" else "4";
|
||||
print("pointer size via if: {}\n", ps);
|
||||
}
|
||||
|
||||
// ── Trailing commas ──────────────────────────────────────────
|
||||
|
||||
55
examples/51-compound-assign-global.sx
Normal file
55
examples/51-compound-assign-global.sx
Normal file
@@ -0,0 +1,55 @@
|
||||
// Test: compound assignment operators on global variables
|
||||
// Ensures += -= *= /= %= &= |= ^= <<= >>= all do load-modify-store
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
g_add : s64 = 10;
|
||||
g_sub : s64 = 10;
|
||||
g_mul : s64 = 10;
|
||||
g_div : s64 = 100;
|
||||
g_mod : s64 = 10;
|
||||
g_and : s64 = 0xff;
|
||||
g_or : s64 = 0x0f;
|
||||
g_xor : s64 = 0xff;
|
||||
g_shl : s64 = 1;
|
||||
g_shr : s64 = 256;
|
||||
|
||||
main :: () -> void {
|
||||
// += repeated: should accumulate, not reset
|
||||
g_add += 1;
|
||||
g_add += 1;
|
||||
g_add += 1;
|
||||
print("add: {}\n", g_add); // 13
|
||||
|
||||
g_sub -= 3;
|
||||
g_sub -= 2;
|
||||
print("sub: {}\n", g_sub); // 5
|
||||
|
||||
g_mul *= 2;
|
||||
g_mul *= 3;
|
||||
print("mul: {}\n", g_mul); // 60
|
||||
|
||||
g_div /= 5;
|
||||
g_div /= 4;
|
||||
print("div: {}\n", g_div); // 5
|
||||
|
||||
g_mod %= 3;
|
||||
print("mod: {}\n", g_mod); // 1
|
||||
|
||||
g_and &= 0x0f;
|
||||
print("and: {}\n", g_and); // 15
|
||||
|
||||
g_or |= 0xf0;
|
||||
print("or: {}\n", g_or); // 255
|
||||
|
||||
g_xor ^= 0x0f;
|
||||
print("xor: {}\n", g_xor); // 240
|
||||
|
||||
g_shl <<= 4;
|
||||
g_shl <<= 2;
|
||||
print("shl: {}\n", g_shl); // 64
|
||||
|
||||
g_shr >>= 3;
|
||||
g_shr >>= 2;
|
||||
print("shr: {}\n", g_shr); // 8
|
||||
}
|
||||
@@ -706,11 +706,15 @@ pub const LLVMEmitter = struct {
|
||||
if (ptr_kind == c.LLVMPointerTypeKind and val_kind != c.LLVMVoidTypeKind) {
|
||||
// Coerce value to match the IR-declared pointer target type.
|
||||
// E.g. storing i64 to *i8 (from index_gep on string) needs truncation.
|
||||
//
|
||||
// Only unwrap .pointer (from index_gep/alloca: *element → element).
|
||||
// Never unwrap .many_pointer — it only appears as struct_gep field
|
||||
// value types (e.g., [*]BigNode), where unwrapping to the element
|
||||
// type gives a wrong store size (stores BigNode-sized instead of ptr).
|
||||
if (self.getRefIRType(st.ptr)) |ptr_ir_ty| {
|
||||
const pointee_info = self.ir_mod.types.get(ptr_ir_ty);
|
||||
const target_ty: ?c.LLVMTypeRef = switch (pointee_info) {
|
||||
.pointer => |p| self.toLLVMType(p.pointee),
|
||||
.many_pointer => |p| self.toLLVMType(p.element),
|
||||
else => null,
|
||||
};
|
||||
if (target_ty) |tt| {
|
||||
@@ -2566,6 +2570,7 @@ pub const LLVMEmitter = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// Coerce both binary operands to match the instruction's result type.
|
||||
/// E.g. if result is i64 but one operand is i32, sext it.
|
||||
fn matchBinOpTypes(self: *LLVMEmitter, lhs: *c.LLVMValueRef, rhs: *c.LLVMValueRef, result_ty: TypeId) void {
|
||||
|
||||
@@ -1143,12 +1143,19 @@ pub const Lowering = struct {
|
||||
// Fallback: global variable assignment
|
||||
if (!handled) {
|
||||
if (self.global_names.get(id.name)) |gi| {
|
||||
const val_ty = self.builder.getRefType(val);
|
||||
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
|
||||
self.coerceToType(val, val_ty, gi.ty)
|
||||
else
|
||||
val;
|
||||
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = store_val } }, .void);
|
||||
if (asgn.op == .assign) {
|
||||
const val_ty = self.builder.getRefType(val);
|
||||
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
|
||||
self.coerceToType(val, val_ty, gi.ty)
|
||||
else
|
||||
val;
|
||||
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = store_val } }, .void);
|
||||
} else {
|
||||
// Compound assignment: load current value, apply op, store back
|
||||
const loaded = self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
||||
const result = self.emitCompoundOp(loaded, val, asgn.op, gi.ty);
|
||||
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = result } }, .void);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1228,7 +1235,12 @@ pub const Lowering = struct {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty);
|
||||
// Wrap in ptrTo so the store handler sees *field_ty (consistent
|
||||
// with index_gep which uses ptrTo(elem_ty)). Without this, a
|
||||
// [*]BigNode field makes the store handler extract BigNode as the
|
||||
// target type, storing element-sized bytes instead of a pointer.
|
||||
const gep_ty = self.module.types.ptrTo(field_ty);
|
||||
const gep = self.builder.structGepTyped(obj_ptr, field_idx, gep_ty, obj_ty);
|
||||
// Coerce value to field type
|
||||
const src_ty = self.inferExprType(asgn.value);
|
||||
const coerced = self.coerceToType(val, src_ty, field_ty);
|
||||
@@ -1434,6 +1446,13 @@ pub const Lowering = struct {
|
||||
break :blk binding.ref;
|
||||
}
|
||||
}
|
||||
// Check compile-time constants (OS, ARCH, POINTER_SIZE) before globals
|
||||
if (self.comptime_constants.get(id.name)) |cv| {
|
||||
switch (cv) {
|
||||
.int_val => |iv| break :blk self.builder.constInt(iv, .s64),
|
||||
.enum_tag => |et| break :blk self.builder.constInt(@intCast(et.tag), et.ty),
|
||||
}
|
||||
}
|
||||
// Check globals (#run constants)
|
||||
if (self.global_names.get(id.name)) |gi| {
|
||||
break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
||||
@@ -2672,7 +2691,7 @@ pub const Lowering = struct {
|
||||
for (lowered.items) |l| {
|
||||
if (std.mem.eql(u8, l.name, sf_name)) {
|
||||
var val = l.val;
|
||||
const src_ty = self.inferExprType(l.node);
|
||||
const src_ty = self.builder.getRefType(val);
|
||||
val = self.coerceToType(val, src_ty, sf.ty);
|
||||
fields.append(self.alloc, val) catch unreachable;
|
||||
found = true;
|
||||
@@ -3548,10 +3567,32 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle closure(lambda) — just return the lambda's closure_create result
|
||||
// Handle closure(fn_or_lambda) — wrap bare functions into closures
|
||||
if (c.callee.data == .identifier and std.mem.eql(u8, c.callee.data.identifier.name, "closure")) {
|
||||
if (c.args.len >= 1) {
|
||||
return self.lowerExpr(c.args[0]);
|
||||
const arg = c.args[0];
|
||||
// If argument is a bare function name, create a proper closure from it
|
||||
if (arg.data == .identifier) {
|
||||
const fn_name = arg.data.identifier.name;
|
||||
if (!self.lowered_functions.contains(fn_name)) {
|
||||
self.lazyLowerFunction(fn_name);
|
||||
}
|
||||
if (self.resolveFuncByName(fn_name)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
// Build closure type from function signature
|
||||
var param_types_list = std.ArrayList(TypeId).empty;
|
||||
defer param_types_list.deinit(self.alloc);
|
||||
for (func.params) |p| {
|
||||
param_types_list.append(self.alloc, p.ty) catch unreachable;
|
||||
}
|
||||
const closure_ty = self.module.types.closureType(param_types_list.items, func.ret);
|
||||
const closure_info = self.module.types.get(closure_ty).closure;
|
||||
const tramp_id = self.createBareFnTrampoline(fid, closure_info);
|
||||
return self.builder.closureCreate(tramp_id, Ref.none, closure_ty);
|
||||
}
|
||||
}
|
||||
// Lambda or other expression — already produces closure_create
|
||||
return self.lowerExpr(arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6343,18 +6384,31 @@ pub const Lowering = struct {
|
||||
|
||||
/// Check if a match expression is a type-category match (patterns are type/category names).
|
||||
fn inferMatchResultType(self: *Lowering, me: *const ast.MatchExpr) TypeId {
|
||||
// Infer result type from the first non-default arm body
|
||||
// Infer result type from the first non-null arm body.
|
||||
// If we skip null_literal arms and find a concrete type T, and there
|
||||
// were null arms, the result is ?T (optional).
|
||||
var has_null = false;
|
||||
for (me.arms) |arm| {
|
||||
if (arm.body.data == .block) {
|
||||
// Block — check last statement
|
||||
const last_node = if (arm.body.data == .block) blk: {
|
||||
if (arm.body.data.block.stmts.len > 0) {
|
||||
const last = arm.body.data.block.stmts[arm.body.data.block.stmts.len - 1];
|
||||
return self.inferExprType(last);
|
||||
break :blk arm.body.data.block.stmts[arm.body.data.block.stmts.len - 1];
|
||||
}
|
||||
break :blk arm.body;
|
||||
} else arm.body;
|
||||
|
||||
if (last_node.data == .null_literal) {
|
||||
has_null = true;
|
||||
continue;
|
||||
}
|
||||
return self.inferExprType(arm.body);
|
||||
|
||||
// First non-null arm determines the type (same as old behavior)
|
||||
const arm_ty = self.inferExprType(last_node);
|
||||
if (has_null and arm_ty != .void) {
|
||||
return self.module.types.optionalOf(arm_ty);
|
||||
}
|
||||
return arm_ty;
|
||||
}
|
||||
return .s64;
|
||||
return .void;
|
||||
}
|
||||
|
||||
fn isTypeCategoryMatch(me: *const ast.MatchExpr) bool {
|
||||
|
||||
@@ -515,6 +515,11 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
};
|
||||
timer.record("link");
|
||||
|
||||
// Post-process wasm HTML: inject content hash for cache busting
|
||||
if (merged_config.isEmscripten() and std.mem.endsWith(u8, final_output, ".html")) {
|
||||
sx.target.postProcessWasmHtml(allocator, io, final_output);
|
||||
}
|
||||
|
||||
// Save linked binary to cache
|
||||
if (enable_cache) {
|
||||
std.Io.Dir.copyFile(.cwd(), output_path, .cwd(), cache_bin, io, .{ .make_path = true }) catch {};
|
||||
|
||||
@@ -270,6 +270,74 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
if (result.exited != 0) return error.LinkError;
|
||||
}
|
||||
|
||||
/// After emcc produces HTML output, inject cache-busting hashes into the
|
||||
/// generated <script> tag and add Module.locateFile for .wasm/.data files.
|
||||
pub fn postProcessWasmHtml(allocator: std.mem.Allocator, io: std.Io, html_path: []const u8) void {
|
||||
const base = if (std.mem.endsWith(u8, html_path, ".html"))
|
||||
html_path[0 .. html_path.len - 5]
|
||||
else
|
||||
return;
|
||||
|
||||
// Hash build output contents (.js + .wasm + optional .data)
|
||||
var hash: u64 = 0;
|
||||
const exts = [_][]const u8{ ".js", ".wasm", ".data" };
|
||||
for (exts) |ext| {
|
||||
const path = std.fmt.allocPrint(allocator, "{s}{s}", .{ base, ext }) catch continue;
|
||||
if (std.Io.Dir.readFileAlloc(.cwd(), io, path, allocator, .limited(64 * 1024 * 1024))) |data| {
|
||||
hash = std.hash.Wyhash.hash(hash, data);
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
const hash_hex = std.fmt.allocPrint(allocator, "{x:0>8}", .{@as(u32, @truncate(hash))}) catch return;
|
||||
|
||||
// Read the final HTML produced by emcc
|
||||
const html = std.Io.Dir.readFileAlloc(.cwd(), io, html_path, allocator, .limited(10 * 1024 * 1024)) catch return;
|
||||
|
||||
var out = std.ArrayList(u8).empty;
|
||||
var pos: usize = 0;
|
||||
var injected_locateFile = false;
|
||||
|
||||
// Find emcc's generated script tag: <script ...src="*.js"></script>
|
||||
// Inject ?v=HASH into the src and prepend a Module.locateFile script.
|
||||
while (std.mem.indexOfPos(u8, html, pos, "src=\"")) |src_start| {
|
||||
const val_start = src_start + 5; // past src="
|
||||
const val_end = std.mem.indexOfPos(u8, html, val_start, "\"") orelse break;
|
||||
const src_val = html[val_start..val_end];
|
||||
|
||||
if (!std.mem.endsWith(u8, src_val, ".js")) {
|
||||
// Not a .js src — skip past this attribute and keep searching
|
||||
pos = val_end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the opening < of this tag to inject locateFile before it
|
||||
const tag_start = if (std.mem.lastIndexOf(u8, html[pos..src_start], "<")) |off| pos + off else src_start;
|
||||
|
||||
// Copy everything up to the tag start
|
||||
out.appendSlice(allocator, html[pos..tag_start]) catch return;
|
||||
|
||||
// Inject Module.locateFile once, before the first .js script tag
|
||||
if (!injected_locateFile) {
|
||||
out.appendSlice(allocator, "<script>Module.locateFile=function(p){return p+'?v=") catch return;
|
||||
out.appendSlice(allocator, hash_hex) catch return;
|
||||
out.appendSlice(allocator, "'}</script>\n") catch return;
|
||||
injected_locateFile = true;
|
||||
}
|
||||
|
||||
// Copy tag up to the closing quote of src, inserting ?v=HASH
|
||||
out.appendSlice(allocator, html[tag_start..val_end]) catch return;
|
||||
out.appendSlice(allocator, "?v=") catch return;
|
||||
out.appendSlice(allocator, hash_hex) catch return;
|
||||
|
||||
pos = val_end;
|
||||
}
|
||||
// Copy remaining HTML
|
||||
out.appendSlice(allocator, html[pos..]) catch return;
|
||||
|
||||
const final = out.toOwnedSlice(allocator) catch return;
|
||||
std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = html_path, .data = final }) catch {};
|
||||
}
|
||||
|
||||
/// Common library paths for the host OS, computed at comptime.
|
||||
pub const host_lib_paths = blk: {
|
||||
const builtin = @import("builtin");
|
||||
|
||||
@@ -139,6 +139,7 @@ ptr!=null: false
|
||||
ptr2==null: false
|
||||
ptr2!=null: true
|
||||
ptr-nested-field: 1.000000 2.000000 3.000000
|
||||
mp-store-sentinel: 42
|
||||
vec-construct: [1.000000, 3.000000, 2.000000]
|
||||
vec-add: [5.000000, 7.000000, 9.000000]
|
||||
vec-sub: [4.000000, 3.000000, 2.000000]
|
||||
@@ -609,6 +610,7 @@ usize->s64: 42
|
||||
not wasm
|
||||
known os
|
||||
desktop 64-bit
|
||||
pointer size via if: 8
|
||||
=== Trailing Commas ===
|
||||
trailing commas ok
|
||||
=== DONE ===
|
||||
|
||||
Reference in New Issue
Block a user