diff options
author | 2022-03-27 05:38:39 -0400 | |
---|---|---|
committer | 2022-03-27 02:38:39 -0700 | |
commit | 0132b7164e956d56bf74dd8c45ad4cf90769961b (patch) | |
tree | 0dce8a77a616830b8a6e75d1d5505c2bb180e734 | |
parent | 2fc881540b680f28486bd0f597eb3a1262c2ea8b (diff) | |
download | bun-0132b7164e956d56bf74dd8c45ad4cf90769961b.tar.gz bun-0132b7164e956d56bf74dd8c45ad4cf90769961b.tar.zst bun-0132b7164e956d56bf74dd8c45ad4cf90769961b.zip |
Bun.mmap() (#134)
* Bun.mmap()
* nitpicks
-rw-r--r-- | integration/bunjs-only-snippets/mmap.test.js | 49 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 51 | ||||
-rw-r--r-- | src/javascript/jsc/node/syscall.zig | 70 |
3 files changed, 170 insertions, 0 deletions
diff --git a/integration/bunjs-only-snippets/mmap.test.js b/integration/bunjs-only-snippets/mmap.test.js new file mode 100644 index 000000000..03ce75e87 --- /dev/null +++ b/integration/bunjs-only-snippets/mmap.test.js @@ -0,0 +1,49 @@ +import { describe, it, expect } from "bun:test"; + +const path = `/tmp/bun-mmap-test_${Math.random()}.txt`; + +await Bun.write(path, "hello"); + +it("mmap finalizer", async () => { + let map = Bun.mmap(path); + const map2 = Bun.mmap(path); + + map = null; + Bun.gc(true); + await new Promise(resolve => setTimeout(resolve, 1)); +}); + +it('mmap passed to other syscalls', async () => { + const map = Bun.mmap(path); + await Bun.write(path + '1', map); + const text = await (await Bun.file(path + '1')).text(); + + expect(text).toBe(new TextDecoder().decode(map)); +}); + +it("mmap sync", async () => { + const map = Bun.mmap(path); + const map2 = Bun.mmap(path); + + const old = map[0]; + + map[0] = 0; + expect(map2[0]).toBe(0); + + map2[0] = old; + expect(map[0]).toBe(old); + + await Bun.write(path, "olleh"); + expect(new TextDecoder().decode(map)).toBe("olleh"); +}); + +it("mmap private", () => { + const map = Bun.mmap(path, { shared: true }); + const map2 = Bun.mmap(path, { shared: false }); + + const old = map[0]; + + map2[0] = 0; + expect(map2[0]).toBe(0); + expect(map[0]).toBe(old); +});
\ No newline at end of file diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index 0eaab1dbb..b43771f07 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -1019,6 +1019,10 @@ pub const Class = NewClass( .rfn = Bun.allocUnsafe, .ts = .{}, }, + .mmap = .{ + .rfn = Bun.mmapFile, + .ts = .{}, + }, .generateHeapSnapshot = .{ .rfn = Bun.generateHeapSnapshot, .ts = d.ts{}, @@ -1158,6 +1162,53 @@ pub fn allocUnsafe( ).toJSObjectRef(ctx, null); } +pub fn mmapFile( + _: void, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, +) js.JSValueRef { + var args = JSC.Node.ArgumentsSlice.from(arguments); + + var buf: [bun.MAX_PATH_BYTES]u8 = undefined; + const path = getFilePath(ctx, arguments[0..@minimum(1, arguments.len)], &buf, exception) orelse return null; + + buf[path.len] = 0; + + const buf_z: [:0]const u8 = buf[0..path.len :0]; + + const flags = v: { + _ = args.nextEat(); + const opts = args.nextEat() orelse break :v std.os.MAP.SHARED; + const sync = opts.get(ctx.ptr(), "sync") orelse JSC.JSValue.jsBoolean(false); + const shared = opts.get(ctx.ptr(), "shared") orelse JSC.JSValue.jsBoolean(true); + const sync_flags = if (@hasDecl(std.os, "SYNC")) std.os.MAP.SYNC | std.os.MAP.SHARED_VALIDATE else 0; + + var flags: u32 = 0; + if (sync.toBoolean()) flags |= sync_flags; + if (shared.toBoolean()) flags |= std.os.MAP.SHARED else flags |= std.os.MAP.PRIVATE; + + break :v flags; + }; + + const map = switch (JSC.Node.Syscall.mmapFile(buf_z, flags)) { + .result => |map| map, + + .err => |err| { + exception.* = err.toJS(ctx); + return null; + }, + }; + + return JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(ctx, JSC.C.JSTypedArrayType.kJSTypedArrayTypeUint8Array, @ptrCast(?*anyopaque, map.ptr), map.len, struct { + pub fn x(ptr: ?*anyopaque, size: ?*anyopaque) callconv(.C) void { + _ = JSC.Node.Syscall.munmap(@ptrCast([*]align(std.mem.page_size) u8, @alignCast(std.mem.page_size, ptr))[0..@ptrToInt(size)]); + } + }.x, @intToPtr(?*anyopaque, map.len), exception); +} + pub fn getTranspilerConstructor( _: void, ctx: js.JSContextRef, diff --git a/src/javascript/jsc/node/syscall.zig b/src/javascript/jsc/node/syscall.zig index 75ea5be89..bf4d6189e 100644 --- a/src/javascript/jsc/node/syscall.zig +++ b/src/javascript/jsc/node/syscall.zig @@ -51,6 +51,8 @@ pub const Tag = enum(u8) { lutimes, mkdir, mkdtemp, + mmap, + munmap, open, pread, pwrite, @@ -399,6 +401,74 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) { } } +/// Use of a mapped region can result in these signals: +/// * SIGSEGV - Attempted write into a region mapped as read-only. +/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file +fn sysmmap( + ptr: ?[*]align(mem.page_size) u8, + length: usize, + prot: u32, + flags: u32, + fd: os.fd_t, + offset: u64, +) Maybe([]align(mem.page_size) u8) { + const mmap_sym = if (builtin.os.tag == .linux and builtin.link_libc) + system.mmap64 + else + system.mmap; + + const ioffset = @bitCast(i64, offset); // the OS treats this as unsigned + const rc = mmap_sym(ptr, length, prot, flags, fd, ioffset); + + _ = if (builtin.link_libc) blk: { + if (rc != std.c.MAP.FAILED) return .{ .result = @ptrCast([*]align(mem.page_size) u8, @alignCast(mem.page_size, rc))[0..length] }; + break :blk rc; + } else blk: { + const err = os.errno(rc); + if (err == .SUCCESS) return .{ .result = @intToPtr([*]align(mem.page_size) u8, rc)[0..length] }; + break :blk rc; + }; + + return Maybe([]align(mem.page_size) u8).errnoSys(@ptrToInt(rc), .mmap).?; +} + +pub fn mmapFile(path: [:0]const u8, flags: u32) Maybe([]align(mem.page_size) u8) { + const fd = switch (open(path, os.O.RDWR, 0)) { + .result => |fd| fd, + .err => |err| return .{ .err = err }, + }; + + const size = switch (stat(path)) { + .result => |stat| stat.size, + .err => |err| { + _ = close(fd); + return .{ .err = err }; + }, + }; + + const map = switch (sysmmap(null, @intCast(usize, size), os.PROT.READ | os.PROT.WRITE, flags, fd, 0)) { + .result => |map| map, + + .err => |err| { + _ = close(fd); + return .{ .err = err }; + }, + }; + + if (close(fd)) |err| { + _ = munmap(map); + return .{ .err = err }; + } + + return .{ .result = map }; +} + +pub fn munmap(memory: []align(mem.page_size) const u8) Maybe(void) { + if (Maybe(void).errnoSys(system.munmap(memory.ptr, memory.len), .munmap)) |err| { + return err; + } else return Maybe(void).success; +} + pub const Error = struct { const max_errno_value = brk: { const errno_values = std.enums.values(os.E); |