a touch more fiddling

This commit is contained in:
koniifer 2024-09-10 00:10:10 +01:00
parent fd155ea26a
commit 3b95371c41
11 changed files with 239 additions and 164 deletions

53
Cargo.lock generated
View file

@ -166,15 +166,6 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -211,7 +202,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
@ -391,17 +381,17 @@ dependencies = [
[[package]]
name = "hbbytecode"
version = "0.1.0"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e8a5027cab7ce008dbb0964fe0522c8fef585979"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#67b8ffe2f223629850c293858abbb0cc5337a850"
[[package]]
name = "hbbytecode"
version = "0.1.0"
source = "git+https://git.ablecorp.us/ableos/holey-bytes#e8a5027cab7ce008dbb0964fe0522c8fef585979"
source = "git+https://git.ablecorp.us/ableos/holey-bytes#67b8ffe2f223629850c293858abbb0cc5337a850"
[[package]]
name = "hblang"
version = "0.1.0"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e8a5027cab7ce008dbb0964fe0522c8fef585979"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#67b8ffe2f223629850c293858abbb0cc5337a850"
dependencies = [
"hbvm 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
]
@ -409,7 +399,7 @@ dependencies = [
[[package]]
name = "hbvm"
version = "0.1.0"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e8a5027cab7ce008dbb0964fe0522c8fef585979"
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#67b8ffe2f223629850c293858abbb0cc5337a850"
dependencies = [
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
]
@ -417,7 +407,7 @@ dependencies = [
[[package]]
name = "hbvm"
version = "0.1.0"
source = "git+https://git.ablecorp.us/ableos/holey-bytes#e8a5027cab7ce008dbb0964fe0522c8fef585979"
source = "git+https://git.ablecorp.us/ableos/holey-bytes#67b8ffe2f223629850c293858abbb0cc5337a850"
dependencies = [
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/ableos/holey-bytes)",
]
@ -507,9 +497,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
dependencies = [
"bytes",
"futures-channel",
@ -594,16 +584,14 @@ name = "kernel"
version = "0.2.0"
dependencies = [
"crossbeam-queue",
"crossbeam-utils",
"derive_more",
"embedded-graphics",
"hashbrown",
"hbvm 0.1.0 (git+https://git.ablecorp.us/ableos/holey-bytes)",
"kiam",
"limine",
"log",
"rdrand",
"sbi",
"slab",
"spin",
"uart_16550",
"versioning",
@ -613,12 +601,6 @@ dependencies = [
"xml",
]
[[package]]
name = "kiam"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0fc32485e41ae5e9dedd1442f36dec998431adc9f321224c2c5d645f38fdcb"
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -907,15 +889,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "rdrand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92195228612ac8eed47adbc2ed0f04e513a4ccb98175b6f2bd04d963b533655"
dependencies = [
"rand_core",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
@ -1046,9 +1019,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
[[package]]
name = "rustls-webpki"
version = "0.102.7"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
@ -1389,12 +1362,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-xid"
version = "0.2.5"

View file

@ -9,13 +9,11 @@ embedded-graphics = "0.8"
hbvm.git = "https://git.ablecorp.us/ableos/holey-bytes"
log = "0.4"
spin = "0.9"
uart_16550 = "0.3"
slab = { version = "0.4", default-features = false }
uart_16550 = { version = "0.3", features = ["nightly"] }
xml.git = "https://git.ablecorp.us/ableos/ableos_userland"
versioning.git = "https://git.ablecorp.us/ableos/ableos_userland"
# able_graphics_library.git = "https://git.ablecorp.us/ableos/ableos_userland"
hashbrown = "0.14"
kiam = "0.1"
hashbrown = { version = "0.14", features = ["nightly"] }
[dependencies.limine]
version = "0.1"
@ -24,7 +22,11 @@ version = "0.1"
[dependencies.crossbeam-queue]
version = "0.3"
default-features = false
features = ["alloc"]
features = ["alloc", "nightly"]
[dependencies.crossbeam-utils]
version = "0.8"
default-features = false
# [dependencies.clparse]
# git = "https://git.ablecorp.us/ableos/ableos_userland"
@ -51,9 +53,6 @@ features = [
x86_64 = "0.15"
x2apic = "0.4"
virtio-drivers = "0.7"
# rdrand = "*"
rdrand = { version = "0.8", default-features = false }
[target.'cfg(target_arch = "riscv64")'.dependencies]
sbi = "0.2.0"

View file

@ -1,8 +1,10 @@
use core::num;
use alloc::boxed::Box;
use spin::{Mutex, Once};
use crate::memory::{MemoryManager, PhysicalAddress, VirtualAddress};
use {
crate::memory::{MemoryManager, PhysicalAddress, VirtualAddress},
alloc::boxed::Box,
spin::{Mutex, Once},
};
use super::PAGE_SIZE;
@ -28,7 +30,7 @@ impl PageSize {
}
pub struct PageTable {
entries: [PageEntry; 512]
entries: [PageEntry; 512],
}
impl PageTable {
@ -72,8 +74,14 @@ impl PageTable {
/// flags MUST include one or more of the following:
/// Read, Write, Execute
/// The valid bit automatically gets added
pub fn map(&mut self, vaddr: VirtualAddress, paddr: PhysicalAddress, flags: PageEntryFlags, page_size: PageSize) {
assert!(flags as usize & 0xe != 0);
pub fn map(
&mut self,
vaddr: VirtualAddress,
paddr: PhysicalAddress,
flags: PageEntryFlags,
page_size: PageSize,
) {
assert!(flags as usize & 0xE != 0);
let vpn = vaddr.vpns();
let ppn = paddr.ppns();
@ -91,7 +99,7 @@ impl PageTable {
}
let entry = v.addr().as_mut_ptr::<PageEntry>();
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
}
// When we get here, we should be at VPN[0] and v should be pointing to our entry.
@ -105,14 +113,24 @@ impl PageTable {
}
/// Identity maps a page of memory
pub fn identity_map(&mut self, addr: PhysicalAddress, flags: PageEntryFlags, page_size: PageSize) {
pub fn identity_map(
&mut self,
addr: PhysicalAddress,
flags: PageEntryFlags,
page_size: PageSize,
) {
// log::debug!("identity mapped {addr}");
self.map(addr.as_addr().into(), addr, flags, page_size);
}
/// Identity maps a range of contiguous memory
/// This assumes that start <= end
pub fn identity_map_range(&mut self, start: PhysicalAddress, end: PhysicalAddress, flags: PageEntryFlags) {
pub fn identity_map_range(
&mut self,
start: PhysicalAddress,
end: PhysicalAddress,
flags: PageEntryFlags,
) {
log::debug!("start: {start}, end: {end}");
let mut mem_addr = start.as_addr() & !(PAGE_SIZE - 1);
let num_pages = (align_val(end.as_addr(), 12) - mem_addr - 1) / PAGE_SIZE + 1;
@ -142,7 +160,7 @@ impl PageTable {
}
let entry = v.addr().as_mut_ptr::<PageEntry>();
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
}
// If we're here this is an unmapped page
@ -182,7 +200,7 @@ pub enum PageEntryFlags {
Global = 1 << 5,
Access = 1 << 6,
Dirty = 1 << 7,
// for convenience
ReadWrite = Self::Read as usize | Self::Write as usize,
ReadExecute = Self::Read as usize | Self::Execute as usize,
@ -228,7 +246,7 @@ impl PageEntry {
}
fn addr(&self) -> PhysicalAddress {
((self.entry() as usize & !0x3ff) << 2).into()
((self.entry() as usize & !0x3FF) << 2).into()
}
fn destroy(&mut self) {

View file

@ -1,4 +1,6 @@
use {crate::bootmodules::BootModule, core::arch::asm, log::warn, rdrand::RdSeed};
use core::arch::x86_64::{_rdrand32_step, _rdrand64_step, _rdseed32_step, _rdseed64_step};
use {crate::bootmodules::BootModule, core::arch::asm, log::warn};
pub mod memory;
mod cpuid;
@ -189,62 +191,43 @@ unsafe extern "C" fn start() -> ! {
)
}
use crossbeam_utils::Backoff;
/// Spin loop
pub fn spin_loop() -> ! {
let backoff = Backoff::new();
loop {
x86_64::instructions::hlt();
core::hint::spin_loop();
// x86_64::instructions::hlt();
backoff.spin()
}
}
pub fn hardware_random_u64() -> u64 {
use {log::trace, rdrand::RdRand};
let gen = RdRand::new();
match gen {
Ok(gen) => {
let ret = gen.try_next_u64().unwrap();
trace!("Random {}", ret);
return ret;
}
Err(_) => {
let mut out: u64 = 0;
match unsafe { _rdrand64_step(&mut out) } {
1 => out,
_ => {
warn!("RDRand not supported.");
// Try rdseed
let gen = RdSeed::new();
match gen {
Ok(gen) => {
let ret = gen.try_next_u64().unwrap();
trace!("Random {}", ret);
return ret;
}
Err(_) => {
panic!("Neither RDRand or RDSeed are supported")
}
match unsafe { _rdseed64_step(&mut out) } {
1 => out,
_ => panic!("Neither RDRand or RDSeed are supported"),
}
}
}
}
pub fn hardware_random_u32() -> u32 {
use {log::trace, rdrand::RdRand};
let gen = RdRand::new();
match gen {
Ok(gen) => {
let ret = gen.try_next_u32().unwrap();
trace!("Random {}", ret);
return ret;
}
Err(_) => {
let mut out: u32 = 0;
match unsafe { _rdrand32_step(&mut out) } {
1 => out,
_ => {
warn!("RDRand not supported.");
// Try rdseed
let gen = RdSeed::new();
match gen {
Ok(gen) => {
let ret = gen.try_next_u32().unwrap();
trace!("Random {}", ret);
return ret;
}
Err(_) => {
panic!("Neither RDRand or RDSeed are supported")
}
match unsafe { _rdseed32_step(&mut out) } {
1 => out,
_ => panic!("Neither RDRand or RDSeed are supported"),
}
}
}

View file

@ -65,7 +65,7 @@ impl<'p> Drop for ExecThread<'p> {
impl<'p> Future for ExecThread<'p> {
type Output = Result<(), VmRunError>;
#[inline(always)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.vm.run() {
Err(err) => {
@ -110,10 +110,12 @@ impl HandlePageFault for PageFaultHandler {
}
}
#[inline(always)]
const fn stack_layout() -> Layout {
unsafe { Layout::from_size_align_unchecked(STACK_SIZE, 4096) }
}
#[inline(always)]
fn allocate_stack() -> NonNull<u8> {
let layout = stack_layout();
NonNull::new(unsafe { alloc::alloc::alloc_zeroed(layout) })

View file

@ -66,7 +66,7 @@ pub fn kmain(_cmdline: &str, boot_modules: BootModules) -> ! {
fb1.address.as_ptr().unwrap() as *const u8
);
let mut executor = crate::task::Executor::default();
let mut executor = crate::task::Executor::new(256);
let bm_take = boot_modules.len();
unsafe {
for module in boot_modules.into_iter().take(bm_take) {
@ -80,16 +80,17 @@ pub fn kmain(_cmdline: &str, boot_modules: BootModules) -> ! {
log::info!("Spawning {} with arguments \"{}\"", module.path, cmd);
executor.spawn(async move {
let mut thr = ExecThread::new(&module.bytes, Address::new(0));
if cmd_len > 0 {
thr.set_arguments(cmd.as_bytes().as_ptr() as u64, cmd_len);
}
if let Err(e) = thr.await {
log::error!("{e:?}");
}
});
executor
.spawn(async move {
let mut thr = ExecThread::new(&module.bytes, Address::new(0));
if cmd_len > 0 {
thr.set_arguments(cmd.as_bytes().as_ptr() as u64, cmd_len);
}
if let Err(e) = thr.await {
log::error!("{e:?}");
}
})
.unwrap();
}
info!("Random number: {}", hardware_random_u64());

View file

@ -1,23 +1,23 @@
#![allow(unused)]
use {
alloc::{boxed::Box, sync::Arc, task::Wake},
core::{
future::Future,
pin::Pin,
sync::atomic::{AtomicUsize, Ordering},
task::{Context, Poll, Waker},
},
crossbeam_queue::SegQueue,
hashbrown::HashMap,
slab::Slab,
spin::Mutex,
crossbeam_utils::CachePadded,
};
use alloc::vec::Vec;
pub fn yield_now() -> impl Future<Output = ()> {
struct YieldNow(bool);
impl Future for YieldNow {
type Output = ();
#[inline(always)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.0 {
Poll::Ready(())
@ -34,86 +34,168 @@ pub fn yield_now() -> impl Future<Output = ()> {
#[derive(Default)]
pub struct Executor {
tasks: Slab<Task>,
queue: Arc<SegQueue<TaskId>>,
wakers: HashMap<TaskId, Waker>, // Keeping HashMap as it is more performant for lookups.
tasks: Vec<CachePadded<TaskSlot>>,
task_queue: Arc<TaskQueue>,
max_tasks: usize,
}
impl Executor {
pub fn new() -> Self {
pub fn new(max_tasks: usize) -> Self {
Self {
tasks: Slab::new(),
queue: Arc::new(SegQueue::new()),
wakers: HashMap::new(),
tasks: (0..max_tasks)
.map(|_| CachePadded::new(TaskSlot::default()))
.collect(),
task_queue: Arc::new(TaskQueue::new(max_tasks)),
max_tasks,
}
}
pub fn spawn(&mut self, future: impl Future<Output = ()> + Send + 'static) {
let task_id = TaskId(self.tasks.insert(Task::new(future)));
self.queue.push(task_id);
pub fn spawn(&mut self, future: impl Future<Output = ()> + Send + 'static) -> Result<(), ()> {
let task_id = self.task_queue.allocate_task_id().ok_or(())?;
self.tasks[task_id.0].lock.replace(Task::new(future));
self.task_queue.push(task_id);
Ok(())
}
pub fn run(&mut self) {
let mut task_batch = Vec::with_capacity(16);
loop {
while let Some(id) = self.queue.pop() {
if let Some(task) = self.tasks.get_mut(id.0) {
let waker = self.wakers.entry(id).or_insert_with(|| {
Waker::from(Arc::new(TaskWaker {
id,
queue: Arc::clone(&self.queue),
}))
});
let mut cx = Context::from_waker(waker);
if let Poll::Ready(()) = task.poll(&mut cx) {
self.tasks.remove(id.0);
self.wakers.remove(&id);
}
self.task_queue.batch_pop(&mut task_batch);
if task_batch.is_empty() {
if self.task_queue.is_empty() {
break;
} else {
panic!("Attempted to get task from empty slot: {}", id.0);
continue;
}
}
if self.tasks.is_empty() {
break;
for id in task_batch.drain(..) {
let task_slot = &mut self.tasks[id.0];
let mut task_opt = task_slot.lock.take();
if let Some(task) = &mut task_opt {
let waker = task_slot.waker.get_or_insert_with(|| {
Arc::new(TaskWaker::new(id, Arc::clone(&self.task_queue)))
});
let waker = Waker::from(Arc::clone(waker));
let mut cx = Context::from_waker(&waker);
if let Poll::Ready(()) = task.poll(&mut cx) {
task_slot.waker = None;
self.task_queue.free_task_id(id);
} else {
task_slot.lock.replace(task_opt.take().unwrap());
}
}
}
}
}
}
#[derive(Default)]
struct TaskSlot {
lock: Option<Task>,
waker: Option<Arc<TaskWaker>>,
}
struct Task {
future: Pin<Box<dyn Future<Output = ()> + Send>>,
}
impl Task {
#[inline(always)]
pub fn new(future: impl Future<Output = ()> + Send + 'static) -> Self {
log::trace!("New task scheduled");
Self {
future: Box::pin(future),
}
}
#[inline(always)]
fn poll(&mut self, cx: &mut Context) -> Poll<()> {
self.future.as_mut().poll(cx)
}
}
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct TaskId(usize);
struct TaskWaker {
id: TaskId,
queue: Arc<SegQueue<TaskId>>,
id: TaskId,
task_queue: Arc<TaskQueue>,
}
impl TaskWaker {
#[inline(always)]
pub fn new(id: TaskId, task_queue: Arc<TaskQueue>) -> Self {
Self { id, task_queue }
}
}
impl Wake for TaskWaker {
#[inline(always)]
fn wake(self: Arc<Self>) {
log::trace!("Woke Task-{:?}", self.id);
self.queue.push(self.id);
self.task_queue.push(self.id);
}
#[inline(always)]
fn wake_by_ref(self: &Arc<Self>) {
self.queue.push(self.id);
self.task_queue.push(self.id);
}
}
#[derive(Default)]
struct TaskQueue {
queue: SegQueue<TaskId>,
next_task: AtomicUsize,
max_tasks: usize,
free_tasks: SegQueue<TaskId>,
}
impl TaskQueue {
fn new(max_tasks: usize) -> Self {
Self {
queue: SegQueue::new(),
next_task: AtomicUsize::new(0),
max_tasks,
free_tasks: SegQueue::new(),
}
}
fn allocate_task_id(&self) -> Option<TaskId> {
self.free_tasks.pop().or_else(|| {
let id = self.next_task.fetch_add(1, Ordering::Relaxed);
if id < self.max_tasks {
Some(TaskId(id))
} else {
None
}
})
}
#[inline(always)]
fn free_task_id(&self, id: TaskId) {
self.free_tasks.push(id);
}
#[inline(always)]
fn push(&self, id: TaskId) {
self.queue.push(id);
}
#[inline(always)]
fn batch_pop(&self, output: &mut Vec<TaskId>) {
while let Some(id) = self.queue.pop() {
output.push(id);
if output.len() >= output.capacity() {
break;
}
}
}
#[inline(always)]
fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}

View file

@ -5,7 +5,18 @@ edition = "2021"
[dependencies]
str-reader = "0.1"
derive_more = { version = "1", features = ["full"] }
derive_more = { version = "1", default-features = false, features = [
"add",
"add_assign",
"constructor",
"display",
"from",
"into",
"mul",
"mul_assign",
"not",
"sum",
] }
error-stack = "0.5"
fatfs = "0.3"
toml = "0.8"

View file

@ -1,4 +1,4 @@
#![allow(unused)]
// #![allow(unused)]
mod dev;
@ -195,7 +195,7 @@ TERM_BACKDROP={}
let modules = value.get_mut("modules").unwrap().as_table_mut().unwrap();
// let mut real_modules = modules.clone();
modules.into_iter().for_each(|(key, value)| {
modules.into_iter().for_each(|(_, value)| {
if value.is_table() {
let path = get_path_without_boot_prefix(
value.get("path").expect("You must have `path` as a value"),
@ -244,7 +244,7 @@ TERM_BACKDROP={}
let bootdir = fs.root_dir().create_dir("efi")?.create_dir("boot")?;
let mut f = fs.root_dir().create_file("limine.cfg")?;
let a = f.write(limine_str.as_bytes())?;
let _ = f.write(limine_str.as_bytes())?;
drop(f);
io::copy(
@ -446,6 +446,7 @@ enum Target {
Aarch64,
}
#[allow(unused)]
#[derive(Debug, Display)]
enum Error {
#[display("Failed to build the kernel")]
@ -466,6 +467,7 @@ enum Error {
impl Context for Error {}
#[allow(dead_code)]
fn fmt_qemu_err(e: Option<i32>) -> impl Display {
struct W(Option<i32>);
impl Display for W {

View file

@ -23,7 +23,7 @@ fb_width := 1024
fb_height := 768
fb_pixels := fb_width * fb_height
fb_bytes := fb_pixels << 2
copy_pixels := math.min(0x1800, fb_bytes) >> 2
copy_pixels := math.min(0xC000, fb_bytes) >> 2
partitions := fb_pixels / copy_pixels
total_pages := 1 + fb_bytes >> 12
@ -51,12 +51,22 @@ create_back_buffer := fn(): ^Color {
clear := fn(color: Color): void {
n := 0
loop if n >= copy_pixels break else {
loop if n == 512 break else {
*(back_buffer + n) = color
n += 1
}
n = 1
loop if n >= partitions break else {
loop if n == 8 break else {
*(@as(^[Color; 512], @bitcast(back_buffer)) + n) = *@as(^[Color; 512], @bitcast(back_buffer))
n += 1
}
n = 1
loop if n == copy_pixels >> 12 break else {
*(@as(^[Color; 4096], @bitcast(back_buffer)) + n) = *@as(^[Color; 4096], @bitcast(back_buffer))
n += 1
}
n = 1
loop if n == partitions break else {
*(@as(^[Color; copy_pixels], @bitcast(back_buffer)) + n) = *@as(^[Color; copy_pixels], @bitcast(back_buffer))
n += 1
}
@ -65,7 +75,7 @@ clear := fn(color: Color): void {
sync := fn(): void {
n := 0
loop if n >= partitions break else {
loop if n == partitions break else {
*(@as(^[Color; copy_pixels], @bitcast(front_buffer)) + n) = *(@as(^[Color; copy_pixels], @bitcast(back_buffer)) + n)
n += 1
}

View file

@ -1,4 +1,4 @@
.{example} := @use("./examples/lines.hb")
.{example} := @use("./examples/amogus.hb")
main := fn(): void {
@inline(example)