diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0773e96 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 + +FROM oven/bun:1 AS base +WORKDIR /app + +FROM base AS deps +COPY bun.lock package.json ./ +RUN bun install --frozen-lockfile + +FROM deps AS build +COPY . . +RUN bun run build + +FROM base AS runtime +ENV NODE_ENV=production +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile --production + +COPY --from=build /app/.output ./.output + +EXPOSE 8085 + +CMD ["bun", "run", "serve"] diff --git a/app.config.ts b/app.config.ts deleted file mode 100644 index 7e183c7..0000000 --- a/app.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { defineConfig } from "@solidjs/start/config"; -import tailwindcss from "@tailwindcss/vite"; -//@ts-expect-error -import pkg from "@vinxi/plugin-mdx"; -import { blogPostsPlugin } from "./build-helpers/blogPostsPlugin"; -import remarkFrontmatter from "remark-frontmatter"; -import rehypeMdxCodeProps from "rehype-mdx-code-props"; -import { mdxPrism } from "./build-helpers/mdxPrism"; -import remarkToc from "remark-toc"; - -const { default: mdx } = pkg; -export default defineConfig({ - extensions: ["mdx", "md"], - vite: { - plugins: [ - tailwindcss(), - mdx.withImports({})({ - remarkPlugins: [remarkFrontmatter, remarkToc], - rehypePlugins: [rehypeMdxCodeProps, mdxPrism], - jsx: true, - jsxImportSource: "solid-js", - providerImportSource: "solid-mdx", - }), - blogPostsPlugin(), - ], - build: { - target: "esnext", - }, - }, - server: { - prerender: { - crawlLinks: true, - }, - }, -}); diff --git a/public/resume.pdf b/public/resume.pdf index 67d8fba..25f238f 100644 Binary files a/public/resume.pdf and b/public/resume.pdf differ diff --git a/src/components/Experience.jsx b/src/components/Experience.jsx index 8dae0bf..163b151 100644 --- a/src/components/Experience.jsx +++ b/src/components/Experience.jsx @@ -6,10 +6,10 @@ const Experience = () => { const [jobs, _] = createSignal([ { title: "Software Developer Engineer Intern", - company: "Amazon Canada (Delivery Extensions Team)", + company: "Amazon Canada", location: "Toronto, Ontario, Canada", - range: "May 2025 - July 2025", - url: "https://amazon.jobs/content/en/teams/ftr", + range: "May 2025 - August 2025", + url: "https://flex.amazon.ca/", }, { title: "Software Engineer Intern", diff --git a/src/data/posts.json b/src/data/posts.json index 522c81a..4229a8a 100644 --- a/src/data/posts.json +++ b/src/data/posts.json @@ -1,4 +1,13 @@ [ + { + "title": "A Rusty Stack Jump", + "description": "Jumping into a new stack with Rust", + "date": "2025-02-27", + "featuredImage": null, + "featuredImageDesc": null, + "tags": ["rust", "asm", "systems", "operating systems", "async"], + "slug": "rust_stack_jmp" + }, { "title": "Testing Test Test Test", "description": "Woah this is so cool", diff --git a/src/routes/blog/rust_stack_jmp.mdx b/src/routes/blog/rust_stack_jmp.mdx new file mode 100644 index 0000000..7029081 --- /dev/null +++ b/src/routes/blog/rust_stack_jmp.mdx @@ -0,0 +1,129 @@ +--- +title: A Rusty Stack Jump +description: Jumping into a new stack with Rust +date: 2025-02-27 +featuredImage: +featuredImageDesc: +tags: + - rust + - asm + - systems + - operating systems + - async +--- + +import { Notes, PostImage } from "~/components/Markdown"; +import { Tree } from "~/components/Tree"; + +In my quest to learn to build an async runtime in Rust, I have to learn about CPU context switching. In order to switch from one async task to another, our async runtime has to perform a context switch. This means saving the current CPU registers marked as `callee saved` by the System V ABI manual and loading the CPU registers with our new async stack. + +In this article, I will show you what I have learned about jumping onto a new stack in a x86_64 CPU. + + +I'm learning about async runtimes in Rust based on the amazing book [Asynchronous Programming in Rust: Learn asynchronous programming by building working examples of futures, green threads, and runtimes](https://www.packtpub.com/en-mt/product/asynchronous-programming-in-rust-9781805128137) + +It's an amazing book, don't get me wrong, but I feel like the explanation can be hand-wavy sometimes. Thus, I write this to archive my own explanation and potentially help other people who also struggle with the subject. + + + + + Most async runtimes in Rust do not use stackful coroutines (which are used by + Go's `gochannel`, Erlang's `processes`) and instead, use state machines to + manage async tasks. + + +## Contents + +
+ +## Setting the stage + +Why do we need to swap the stack of async tasks in a runtime with stackful coroutines ? + +Async tasks, by nature, are paused and resumed. Everytime a task is paused to move into a new task, we would have to save the current context of the task that is running and load the context of the upcoming task. + +## Jumping into the new stack + +Here is the code in its entirely, I'd recommend you run this on the [Rust Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024). I have left comments through out the code so you can get the general idea. + +Note that you have to manually stop the process. + +```rust lang="rust" file="stack_swap.rs" +use core::arch::asm; + +// stack size of 48 bytes so its easy to print the stack before we switch contexts +const SSIZE: isize = 48; + +// a struct that represents our CPU state +// +// This struct will stores the stack pointer +#[derive(Debug, Default)] +#[repr(C)] +struct ThreadContext { + rsp: u64, +} + +// Returning ! means +// it will panic OR runs forever +fn hello() -> ! { + println!("I LOVE WAKING UP ON A NEW STACK!"); + loop {} +} + +// new is a pointer to a ThreadContext +unsafe fn gt_switch(new: *const ThreadContext) { + // inline assembly + asm!( + "mov rsp, [{0} + 0x00]", // move the content of where the new pointer is pointing to, into the rsp register + "ret", // ret pops the return address from our custom stack—in our example, the address of hello. + in(reg) new, + ); +} + +fn main() { + // initialize + let mut ctx = ThreadContext::default(); + + // stack initialize + // ie. 0x10 + let mut stack = vec![0_u8; SSIZE as usize]; + + unsafe { + // we get the bottom of the stack + // remember that the stack grows downward from high memory address to low memory address + // i.e 0x40 -> because 0x30 = 0x40 - 0x10 and 0x30 = SSIZE in decimal + // NOTE: offset() is applied in units of the size of the type that the pointer points to + // in our case, stack is a pointer to u8 (a byte) so offset(SSIZE) == offset(48 bytes) == offset(0x30) + let stack_bottom = stack.as_mut_ptr().offset(SSIZE); + + // we align the bottom of the stack to be 16-byte-aligned + // this is for performance reasons as some CPU instructions (SSE and SIMD) + + // The technicality: 15 is b1111 so if we do (stack_bottom AND !15) we will zero out the bottom 4 bits + // + // we also want the bottom of the stack pointer to point to a byte (8bit or u8) + let sb_aligned = (stack_bottom as usize & !15) as *mut u8; + + // Here, we write the address of the hello function as 64 bits(8 bytes) + // Remember that 16 bytes = 0x10 in hex + // So we go DOWN 10 memory addresses, i.e from 0x40 to 0x30 + // NOTE: 16 bytes down (0x10) even though, the hello function pointer is ONLY 8 bytes + // This is because the System V ABI requires the stack pointer to be always be 16-byte aligned + std::ptr::write(sb_aligned.offset(-16) as *mut u64, hello as u64); + + // we write the stack pointer into the rsp inside context + ctx.rsp = sb_aligned.offset(-16) as u64; + + for i in 0..SSIZE { + println!("mem: {}, val: {}", + sb_aligned.offset(-i as isize) as usize, + *sb_aligned.offset(-i as isize)) + }; + + // we go into the function + // we will write our stack pointer to the cpu stack pointer + // and `ret` will pop that stack pointer + gt_switch(&mut ctx); + } +} +``` diff --git a/src/routes/tags/[id].tsx b/src/routes/tags/[id].tsx index 7ba782b..0b9b69a 100644 --- a/src/routes/tags/[id].tsx +++ b/src/routes/tags/[id].tsx @@ -3,6 +3,7 @@ import { type Component, Show } from "solid-js"; import { posts } from "~/data/posts"; import { Posts } from "~/components/Posts"; import { tags } from "~/data/tags"; +import { Title, Meta } from "@solidjs/meta"; /* * Code from andii.dev @@ -11,6 +12,18 @@ const TagId: Component> = (props) => { const tag = () => tags[props.params.id]; return ( No posts with that tag}> + minhtran_dev - Tag: {tag().id} + + + + + + + +

Tag: {tag().id}