Skip to main content

lasm/
lasm_function.rs

1use std::collections::HashMap;
2#[cfg(feature = "dbg")]
3use std::fmt::Debug;
4use super::{LasmError, Value};
5use std::ffi::{CString, CStr};
6
7#[cfg(feature = "llvm")]
8use inkwell::execution_engine::{JitFunction, ExecutionEngine};
9
10#[cfg(feature = "cranelift")]
11use {cranelift_jit::JITModule, cranelift_module::FuncId};
12
13/// Tag enum to represent the type of a value in a slot
14#[repr(u8)]
15#[derive(Clone, Copy, Debug)]
16pub enum Tag {
17    Int = 0,
18    Float = 1,
19    Ptr = 2,
20    Null = 3,
21}
22
23/// Slot struct to represent a variable's value and type in the JIT execution environment
24#[repr(C)]
25#[derive(Clone, Copy, Debug)]
26pub struct Slot {
27    pub tag: Tag,
28    pub _pad: [u8; 7],
29    pub data: u64,
30}
31
32/// Debug information for a compiled function, including IR and variable mappings (only included when the `dbg` feature is enabled)
33#[cfg(feature = "dbg")]
34#[derive(Debug, Clone, PartialEq)]
35pub struct DebugInfo {
36    #[cfg(feature = "llvm")]
37    pub llvm_ir: String,
38    #[cfg(feature = "cranelift")]
39    pub cranelift_ir: String,
40    #[cfg(feature = "llvm")]
41    pub var_indices: HashMap<String, usize>,
42}
43
44/// Placeholder DebugInfo struct when the `dbg` feature is not enabled, to avoid compilation errors while still allowing the LasmFunction struct to compile without debug info.
45#[cfg(not(feature = "dbg"))]
46#[repr(transparent)]
47#[derive(Clone, Copy)]
48pub struct DebugInfo;
49
50/// Represents a compiled LASM function, containing the JIT-compiled code and variable information. The internal structure varies based on the compilation target (LLVM or Cranelift) and whether debug features are enabled.
51pub struct LasmFunction {
52    #[cfg(feature = "llvm")]
53    jit_fn: JitFunction<'static, unsafe extern "C" fn(*mut Slot, usize) -> i32>,
54    #[cfg(feature = "llvm")]
55    _ee: ExecutionEngine<'static>,
56    #[cfg(feature = "llvm")]
57    _string_storage: Vec<Box<Vec<u8>>>,
58
59    #[cfg(feature = "cranelift")]
60    jit_module: JITModule,
61    #[cfg(feature = "cranelift")]
62    func_id: FuncId,
63
64    #[allow(dead_code)]
65    dbg_info: Option<DebugInfo>,
66
67    variables: Vec<String>,
68}
69
70impl LasmFunction {
71    /// Internal constructor for llvm
72    #[cfg(feature = "llvm")]
73    pub(crate) fn new_llvm(
74        jit_fn: JitFunction<'static, unsafe extern "C" fn(*mut Slot, usize) -> i32>,
75        ee: ExecutionEngine<'static>,
76        variables: Vec<String>,
77        string_storage: Vec<Box<Vec<u8>>>,
78    ) -> Self {
79        Self {
80            jit_fn,
81            _ee: ee,
82            _string_storage: string_storage,
83            variables,
84            dbg_info: None,
85        }
86    }
87
88    /// Internal constructor for cranelift
89    #[cfg(feature = "cranelift")]
90    pub(crate) fn new_cranelift(jit_module: JITModule, func_id: FuncId, variables: Vec<String>) -> Self {
91        Self { jit_module, func_id, variables, dbg_info: None }
92    }
93
94    /// Internal modifier to add debug info (only available when the `dbg` feature is enabled)
95    #[cfg(feature = "dbg")]
96    pub(crate) fn with_debug_info(mut self, dbg_info: DebugInfo) -> Self {
97        self.dbg_info = Some(dbg_info);
98        self
99    }
100
101    /// Returns a unique identifier for this function, which can be used for caching or comparison purposes. 
102    /// The specific value returned depends on the compilation target (LLVM or Cranelift) and is derived from the internal representation of the compiled function.
103    pub fn get_id(&self) -> usize {
104        #[cfg(feature = "llvm")]
105        {
106            (unsafe { self.jit_fn.as_raw() }) as *const () as usize
107        }
108        #[cfg(feature = "cranelift")]
109        {
110            self.func_id.as_u32() as usize
111        }
112    }
113
114    /// Returns a reference to the debug information for this function, if available (only when the `dbg` feature is enabled)
115    #[cfg(feature = "dbg")]
116    pub fn debug_info(&self) -> Option<&DebugInfo> {
117        self.dbg_info.as_ref()
118    }
119
120    /// Returns the generated IR for this function as a string, if available (only when the `dbg` feature is enabled). 
121    /// The specific IR returned depends on the compilation target (LLVM or Cranelift).
122    #[cfg(feature = "dbg")]
123    pub fn get_ir(&self) -> Option<&str> {
124        self.dbg_info.as_ref().map(|info| {
125            #[cfg(feature = "llvm")]
126            { info.llvm_ir.as_str() }
127            #[cfg(feature = "cranelift")]
128            { info.cranelift_ir.as_str() }
129        })
130    }
131
132    /// Returns None for the generated IR when the `dbg` feature is not enabled, as no debug information is stored in this case.
133    #[cfg(not(feature = "dbg"))]
134    pub fn get_ir(&self) -> Option<&str> {
135        None
136    }
137
138    /// Internal call function
139    pub(crate) fn call(&self, variables: &mut HashMap<String, Value>) -> Result<i32, LasmError> {
140        let mut vars_vec: Vec<(String, Value)> = self.variables.iter().map(|var| {
141            let value = variables.get(var).cloned().unwrap_or(Value::Null);
142            (var.clone(), value)
143        }).collect();
144        vars_vec.sort_by(|a, b| a.0.cmp(&b.0));
145
146        let mut slots = Vec::with_capacity(vars_vec.len());
147        for (_, value) in &vars_vec {
148            let slot = match value {
149                Value::Int(i) => Slot { tag: Tag::Int, _pad: [0; 7], data: *i as u64 },
150                Value::Float(f) => Slot { tag: Tag::Float, _pad: [0; 7], data: f.to_bits() },
151                Value::String(s) => {
152                    let c_str = CString::new(s.clone())
153                        .map_err(|_| LasmError::Runtime("Invalid string".to_string()))?;
154                    let ptr = c_str.as_ptr() as *mut u8;
155                    std::mem::forget(c_str);
156                    Slot { tag: Tag::Ptr, _pad: [0; 7], data: ptr as u64 }
157                }
158                Value::Ptr(p) => Slot { tag: Tag::Ptr, _pad: [0; 7], data: *p as u64 },
159                Value::Null => Slot { tag: Tag::Null, _pad: [0; 7], data: 0 },
160            };
161            slots.push(slot);
162        }
163
164        let mut slot_array = slots.into_boxed_slice();
165        let ptr = slot_array.as_mut_ptr();
166        let len = slot_array.len();
167        std::mem::forget(slot_array);
168
169        let exit_code = {
170            #[cfg(feature = "llvm")]
171            {
172                unsafe { self.jit_fn.call(ptr, len) }
173            }
174
175            #[cfg(feature = "cranelift")]
176            {
177                let code = self.jit_module.get_finalized_function(self.func_id);
178                let func: extern "C" fn(*mut Slot, usize) -> i32 = unsafe { std::mem::transmute(code) };
179                func(ptr, len)
180            }
181        };
182
183        let slots_slice = unsafe { std::slice::from_raw_parts(ptr, len) };
184        for i in 0..len {
185            let slot = &slots_slice[i];
186            let value = match slot.tag {
187                Tag::Int => Value::Int(slot.data as i64),
188                Tag::Float => Value::Float(f64::from_bits(slot.data)),
189                Tag::Ptr => Value::Ptr(slot.data as *mut u8),
190                Tag::Null => Value::Null,
191            };
192            variables.insert(vars_vec[i].0.clone(), value);
193        }
194
195        Ok(exit_code)
196    }
197}
198
199#[unsafe(no_mangle)]
200pub extern "C" fn lasm_write(fd: i64, ptr: i64, len: i64) -> i64 {
201    if fd == 1 {
202        unsafe {
203            let slice = std::slice::from_raw_parts(ptr as *const u8, len as usize);
204            if let Ok(s) = std::str::from_utf8(slice) {
205                print!("{}", s);
206                use std::io::Write;
207                let _ = std::io::stdout().flush();
208                return len;
209            }
210        }
211    }
212    #[cfg(unix)]
213    unsafe {
214        return libc::write(fd as i32, ptr as *const libc::c_void, len as usize) as i64;
215    }
216    #[cfg(windows)]
217    unsafe {
218        return libc::write(fd as i32, ptr as *const libc::c_void, len as u32) as i64;
219    }
220    #[cfg(not(any(unix, windows)))]
221    {
222        -1
223    }
224}
225
226#[unsafe(no_mangle)]
227pub extern "C" fn lasm_read(fd: i64, ptr: i64, len: i64) -> i64 {
228    #[cfg(unix)]
229    unsafe {
230        return libc::read(fd as i32, ptr as *mut libc::c_void, len as usize) as i64;
231    }
232    #[cfg(windows)]
233    unsafe {
234        return libc::read(fd as i32, ptr as *mut libc::c_void, len as u32) as i64;
235    }
236    #[cfg(not(any(unix, windows)))]
237    {
238        -1
239    }
240}
241
242#[unsafe(no_mangle)]
243pub extern "C" fn write_float(value: f64) -> i64 {
244    print!("{}", value);
245    0
246}
247
248#[unsafe(no_mangle)]
249pub extern "C" fn lasm_malloc(size: i64) -> i64 {
250    use libc::malloc;
251    if size <= 0 {
252        return 0;
253    }
254    unsafe {
255        malloc(size as usize) as i64
256    }
257}
258
259#[unsafe(no_mangle)]
260pub extern "C" fn lasm_free(ptr: i64) {
261    use libc::free;
262    if ptr == 0 {
263        return;
264    }
265    unsafe {
266        free(ptr as *mut libc::c_void);
267    }
268}
269
270#[unsafe(no_mangle)]
271pub extern "C" fn lasm_sleep(milliseconds: i64) {
272    use std::thread;
273    use std::time::Duration;
274    thread::sleep(Duration::from_millis(milliseconds as u64));
275}
276
277#[unsafe(no_mangle)]
278pub extern "C" fn lasm_time() -> i64 {
279    use std::time::{SystemTime, UNIX_EPOCH};
280    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
281    (now.as_secs() as i64 * 1_000_000_000) + now.subsec_nanos() as i64
282}
283
284#[unsafe(no_mangle)]
285pub extern "C" fn lasm_rand(seed: i64) -> i64 {
286    // Simple LCG: seed = (seed * 1103515245 + 12345) % (1 << 31)
287    let mut s = seed as u32;
288    s = ((s as u64 * 1103515245u64 + 12345) % (1u64 << 31)) as u32;
289    let value = s as f64 / (1u32 << 31) as f64;
290    value.to_bits() as i64
291}
292
293#[unsafe(no_mangle)]
294pub extern "C" fn lasm_atoi(ptr: i64) -> i64 {
295    use std::ffi::CStr;
296    if ptr == 0 {
297        return 0;
298    }
299    unsafe {
300        let c_str = CStr::from_ptr(ptr as *const i8);
301        if let Ok(s) = c_str.to_str() {
302            s.parse().unwrap_or(0)
303        } else {
304            0
305        }
306    }
307}
308
309#[unsafe(no_mangle)]
310pub extern "C" fn lasm_system(cmd_ptr: i64) -> i64 {
311    use std::ffi::CStr;
312    use std::process::Command;
313    
314    #[cfg(unix)]
315    unsafe {
316        let cmd_c = CStr::from_ptr(cmd_ptr as *const i8);
317        if let Ok(cmd_s) = cmd_c.to_str() {
318            match Command::new("sh").arg("-c").arg(cmd_s).status() {
319                Ok(status) => status.code().unwrap_or(-1) as i64,
320                Err(_) => -1,
321            }
322        } else {
323            -1
324        }
325    }
326    #[cfg(windows)]
327    unsafe {
328        let cmd_c = CStr::from_ptr(cmd_ptr as *const i8);
329        if let Ok(cmd_s) = cmd_c.to_str() {
330            match Command::new("cmd").arg("/C").arg(cmd_s).status() {
331                Ok(status) => status.code().unwrap_or(-1) as i64,
332                Err(_) => -1,
333            }
334        } else {
335            -1
336        }
337    }
338}
339
340#[unsafe(no_mangle)]
341pub extern "C" fn lasm_ends_with(str_ptr: i64, suffix_ptr: i64) -> i64 {
342    use std::ffi::CStr;
343    unsafe {
344        let str_c = CStr::from_ptr(str_ptr as *const i8);
345        let suffix_c = CStr::from_ptr(suffix_ptr as *const i8);
346        
347        if let (Ok(str_s), Ok(suffix_s)) = (str_c.to_str(), suffix_c.to_str()) {
348            if str_s.ends_with(suffix_s) { 1 } else { 0 }
349        } else {
350            0
351        }
352    }
353}
354
355#[unsafe(no_mangle)]
356pub extern "C" fn lasm_trim(str_ptr: i64, out_ptr: i64) -> i64 {
357    use std::ffi::CStr;
358    unsafe {
359        let str_c = CStr::from_ptr(str_ptr as *const i8);
360        if let Ok(s) = str_c.to_str() {
361            let trimmed = s.trim();
362            let bytes = trimmed.as_bytes();
363            let len = bytes.len().min(255);
364            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
365            *(out_ptr as *mut u8).add(len) = 0;
366            len as i64
367        } else {
368            0
369        }
370    }
371}
372
373#[unsafe(no_mangle)]
374pub extern "C" fn lasm_trim_start(str_ptr: i64, out_ptr: i64) -> i64 {
375    use std::ffi::CStr;
376    unsafe {
377        let str_c = CStr::from_ptr(str_ptr as *const i8);
378        if let Ok(s) = str_c.to_str() {
379            let trimmed = s.trim_start();
380            let bytes = trimmed.as_bytes();
381            let len = bytes.len().min(255);
382            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
383            *(out_ptr as *mut u8).add(len) = 0;
384            len as i64
385        } else {
386            0
387        }
388    }
389}
390
391#[unsafe(no_mangle)]
392pub extern "C" fn lasm_trim_end(str_ptr: i64, out_ptr: i64) -> i64 {
393    use std::ffi::CStr;
394    unsafe {
395        let str_c = CStr::from_ptr(str_ptr as *const i8);
396        if let Ok(s) = str_c.to_str() {
397            let trimmed = s.trim_end();
398            let bytes = trimmed.as_bytes();
399            let len = bytes.len().min(255);
400            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
401            *(out_ptr as *mut u8).add(len) = 0;
402            len as i64
403        } else {
404            0
405        }
406    }
407}
408
409#[unsafe(no_mangle)]
410pub extern "C" fn lasm_streq(str1_ptr: i64, str2_ptr: i64) -> i64 {
411    use std::ffi::CStr;
412    unsafe {
413        let str1_c = CStr::from_ptr(str1_ptr as *const i8);
414        let str2_c = CStr::from_ptr(str2_ptr as *const i8);
415        if let (Ok(s1), Ok(s2)) = (str1_c.to_str(), str2_c.to_str()) {
416            if s1 == s2 { 1 } else { 0 }
417        } else {
418            0
419        }
420    }
421}
422
423#[unsafe(no_mangle)]
424pub extern "C" fn lasm_isws(str_ptr: i64) -> i64 {
425    use std::ffi::CStr;
426    unsafe {
427        let str_c = CStr::from_ptr(str_ptr as *const i8);
428        if let Ok(s) = str_c.to_str() {
429            if s.trim().is_empty() { 1 } else { 0 }
430        } else {
431            0
432        }
433    }
434}
435
436#[unsafe(no_mangle)]
437#[allow(unused_variables)]
438pub extern "C" fn lasm_syscall(number: i64, arg0: i64, arg1: i64, arg2: i64, arg3: i64, arg4: i64, arg5: i64) -> i64 {
439    #[cfg(target_os = "macos")]
440    {
441        unsafe { libc::syscall(number as i32, arg0, arg1, arg2, arg3, arg4, arg5) as i64 }
442    }
443
444    #[cfg(target_os = "linux")]
445    {
446        unsafe { libc::syscall(number, arg0, arg1, arg2, arg3, arg4, arg5) }
447    }
448    #[cfg(windows)]
449    {
450        match number {
451            1 => { // write
452                if arg0 == 1 { // stdout
453                    let buf = arg1 as *const u8;
454                    let count = arg2 as usize;
455                    unsafe {
456                        let slice = std::slice::from_raw_parts(buf, count);
457                        if let Ok(s) = std::str::from_utf8(slice) {
458                            print!("{}", s);
459                            std::io::Write::flush(&mut std::io::stdout()).ok();
460                            count as i64
461                        } else {
462                            -1
463                        }
464                    }
465                } else {
466                    -1
467                }
468            }
469            0 => { // read
470                if arg0 == 0 { // stdin
471                    let buf = arg1 as *mut u8;
472                    let count = arg2 as usize;
473                    unsafe {
474                        let mut input = String::new();
475                        if std::io::stdin().read_line(&mut input).is_ok() {
476                            let bytes = input.as_bytes();
477                            let len = bytes.len().min(count);
478                            std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
479                            len as i64
480                        } else {
481                            -1
482                        }
483                    }
484                } else {
485                    -1
486                }
487            }
488            _ => -1
489        }
490    }
491    #[cfg(not(any(unix, windows)))]
492    {
493        -1
494    }
495}
496
497#[unsafe(no_mangle)]
498pub extern "C" fn lasm_print_int(value: i64) {
499    print!("{}", value);
500    use std::io::Write;
501    let _ = std::io::stdout().flush();
502}
503
504#[unsafe(no_mangle)]
505pub extern "C" fn lasm_print_float(bits: i64) {
506    let value = f64::from_bits(bits as u64);
507    print!("{}", value);
508    use std::io::Write;
509    let _ = std::io::stdout().flush();
510}
511
512#[unsafe(no_mangle)]
513pub extern "C" fn lasm_print_str(ptr: i64) {
514    use std::ffi::CStr;
515    if ptr == 0 {
516        return;
517    }
518    unsafe {
519        let c_str = CStr::from_ptr(ptr as *const i8);
520        if let Ok(s) = c_str.to_str() {
521            print!("{}", s);
522        }
523    }
524    use std::io::Write;
525    let _ = std::io::stdout().flush();
526}
527
528#[unsafe(no_mangle)]
529pub extern "C" fn lasm_itoa(value: i64, buf: i64, size: i64) -> i64 {
530    if buf == 0 || size <= 0 {
531        return 0;
532    }
533    let s = format!("{}", value);
534    let bytes = s.as_bytes();
535    let len = bytes.len().min(size as usize - 1);
536    unsafe {
537        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len);
538        *(buf as *mut u8).add(len) = 0;
539    }
540    len as i64
541}
542
543#[unsafe(no_mangle)]
544pub extern "C" fn lasm_fadd(a_bits: i64, b_bits: i64) -> i64 {
545    let a = f64::from_bits(a_bits as u64);
546    let b = f64::from_bits(b_bits as u64);
547    (a + b).to_bits() as i64
548}
549
550#[unsafe(no_mangle)]
551pub extern "C" fn lasm_fsub(a_bits: i64, b_bits: i64) -> i64 {
552    let a = f64::from_bits(a_bits as u64);
553    let b = f64::from_bits(b_bits as u64);
554    (a - b).to_bits() as i64
555}
556
557#[unsafe(no_mangle)]
558pub extern "C" fn lasm_fmul(a_bits: i64, b_bits: i64) -> i64 {
559    let a = f64::from_bits(a_bits as u64);
560    let b = f64::from_bits(b_bits as u64);
561    (a * b).to_bits() as i64
562}
563
564#[unsafe(no_mangle)]
565pub extern "C" fn lasm_fdiv(a_bits: i64, b_bits: i64) -> i64 {
566    let a = f64::from_bits(a_bits as u64);
567    let b = f64::from_bits(b_bits as u64);
568    (a / b).to_bits() as i64
569}
570
571#[unsafe(no_mangle)]
572pub extern "C" fn lasm_itof(value: i64) -> i64 {
573    (value as f64).to_bits() as i64
574}
575
576#[unsafe(no_mangle)]
577pub extern "C" fn lasm_ftoi(bits: i64) -> i64 {
578    let f = f64::from_bits(bits as u64);
579    f as i64
580}
581
582#[unsafe(no_mangle)]
583pub extern "C" fn lasm_fmod(a_bits: i64, b_bits: i64) -> i64 {
584    let a = f64::from_bits(a_bits as u64);
585    let b = f64::from_bits(b_bits as u64);
586    (a % b).to_bits() as i64
587}
588
589#[unsafe(no_mangle)]
590pub extern "C" fn lasm_atof(ptr: i64) -> i64 {
591    use std::ffi::CStr;
592    if ptr == 0 {
593        return 0.0f64.to_bits() as i64;
594    }
595    unsafe {
596        let c_str = CStr::from_ptr(ptr as *const i8);
597        if let Ok(s) = c_str.to_str() {
598            if let Ok(f) = s.parse::<f64>() {
599                f.to_bits() as i64
600            } else {
601                0.0f64.to_bits() as i64
602            }
603        } else {
604            0.0f64.to_bits() as i64
605        }
606    }
607}
608
609#[unsafe(no_mangle)]
610pub extern "C" fn lasm_ftoa(bits: i64, buf: i64, size: i64) -> i64 {
611    if buf == 0 || size <= 0 {
612        return 0;
613    }
614    let f = f64::from_bits(bits as u64);
615    let s = format!("{}", f);
616    let bytes = s.as_bytes();
617    let len = bytes.len().min(size as usize - 1);
618    unsafe {
619        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len);
620        *(buf as *mut u8).add(len) = 0;
621    }
622    len as i64
623}
624
625#[unsafe(no_mangle)]
626pub extern "C" fn lasm_memcpy(dst: i64, src: i64, len: i64) {
627    if dst == 0 || src == 0 || len <= 0 {
628        return;
629    }
630    unsafe {
631        std::ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, len as usize);
632    }
633}
634
635#[unsafe(no_mangle)]
636pub extern "C" fn lasm_memset(dst: i64, val: i64, len: i64) {
637    if dst == 0 || len <= 0 {
638        return;
639    }
640    unsafe {
641        std::ptr::write_bytes(dst as *mut u8, val as u8, len as usize);
642    }
643}
644
645#[unsafe(no_mangle)]
646pub extern "C" fn lasm_fmt_time(total_nanos: i64, fmt_ptr: i64, buffer: i64) -> i64 {
647    use std::ffi::CStr;
648    use std::time::{SystemTime, UNIX_EPOCH};
649
650    if buffer == 0 || fmt_ptr == 0 {
651        return 0;
652    }
653
654    let fmt_str = unsafe {
655        match CStr::from_ptr(fmt_ptr as *const i8).to_str() {
656            Ok(s) => s,
657            Err(_) => "%Y-%m-%d %H:%M:%S",
658        }
659    };
660    
661    let total_seconds = if total_nanos == 0 {
662        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
663        now.as_secs()
664    } else {
665        (total_nanos / 1_000_000_000) as u64
666    };
667    
668    let _nanoseconds = if total_nanos == 0 {
669        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
670        now.subsec_nanos()
671    } else {
672        (total_nanos % 1_000_000_000) as u32
673    };
674    
675    let days_since_epoch = total_seconds / 86400;
676    let mut year = 1970;
677    let mut days = days_since_epoch;
678    
679    loop {
680        let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
681        let days_in_year = if is_leap { 366 } else { 365 };
682        if days >= days_in_year {
683            days -= days_in_year;
684            year += 1;
685        } else {
686            break;
687        }
688    }
689    
690    let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
691    let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
692    let mut month = 1;
693    let mut day = days + 1;
694    
695    for (i, &days_in_month) in month_days.iter().enumerate() {
696        let actual_days = if i == 1 && is_leap { days_in_month + 1 } else { days_in_month };
697        if day > actual_days {
698            day -= actual_days;
699            month += 1;
700        } else {
701            break;
702        }
703    }
704    
705    let seconds_in_day = total_seconds % 86400;
706    let hour = seconds_in_day / 3600;
707    let minute = (seconds_in_day % 3600) / 60;
708    let second = seconds_in_day % 60;
709    
710    let mut result = String::new();
711    let mut chars = fmt_str.chars().peekable();
712    
713    while let Some(ch) = chars.next() {
714        if ch == '%' {
715            if let Some(spec) = chars.next() {
716                match spec {
717                    'Y' => result.push_str(&format!("{:04}", year)),
718                    'm' => result.push_str(&format!("{:02}", month)),
719                    'd' => result.push_str(&format!("{:02}", day)),
720                    'H' => result.push_str(&format!("{:02}", hour)),
721                    'M' => result.push_str(&format!("{:02}", minute)),
722                    'S' => result.push_str(&format!("{:02}", second)),
723                    '%' => result.push('%'),
724                    _ => { result.push('%'); result.push(spec); }
725                }
726            } else {
727                result.push('%');
728            }
729        } else {
730            result.push(ch);
731        }
732    }
733    
734    let bytes = result.as_bytes();
735    let len = bytes.len().min(255);
736    
737    unsafe {
738        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer as *mut u8, len);
739        *(buffer as *mut u8).add(len) = 0;
740    }
741    
742    len as i64
743}
744
745#[unsafe(no_mangle)]
746pub extern "C" fn lasm_starts_with(str_ptr: i64, prefix_ptr: i64) -> i64 {
747    use std::ffi::CStr;
748    if str_ptr == 0 || prefix_ptr == 0 {
749        return 0;
750    }
751    unsafe {
752        let str_cstr = CStr::from_ptr(str_ptr as *const i8);
753        let prefix_cstr = CStr::from_ptr(prefix_ptr as *const i8);
754        if let (Ok(s), Ok(p)) = (str_cstr.to_str(), prefix_cstr.to_str()) {
755            if s.starts_with(p) { 1 } else { 0 }
756        } else {
757            0
758        }
759    }
760}
761
762#[unsafe(no_mangle)]
763pub extern "C" fn lasm_str_len(str_ptr: i64) -> i64 {
764    use std::ffi::CStr;
765    if str_ptr == 0 {
766        return 0;
767    }
768    unsafe {
769        CStr::from_ptr(str_ptr as *const i8).to_bytes().len() as i64
770    }
771}
772
773#[unsafe(no_mangle)]
774pub extern "C" fn lasm_strcmp(str1_ptr: i64, str2_ptr: i64) -> i64 {
775    if str1_ptr == 0 || str2_ptr == 0 {
776        return if str1_ptr == str2_ptr { 0 } else { 1 };
777    }
778    unsafe {
779        let s1 = CStr::from_ptr(str1_ptr as *const i8).to_bytes();
780        let s2 = CStr::from_ptr(str2_ptr as *const i8).to_bytes();
781        s1.cmp(s2) as i64
782    }
783}
784
785#[unsafe(no_mangle)]
786pub extern "C" fn lasm_strcpy(dst: i64, src: i64) -> i64 {
787    if dst == 0 || src == 0 {
788        return 0;
789    }
790    unsafe {
791        let src_str = CStr::from_ptr(src as *const i8).to_bytes();
792        let dst_slice = std::slice::from_raw_parts_mut(dst as *mut u8, src_str.len() + 1);
793        dst_slice[..src_str.len()].copy_from_slice(src_str);
794        dst_slice[src_str.len()] = 0;
795        dst
796    }
797}
798
799#[unsafe(no_mangle)]
800pub extern "C" fn lasm_flush(fd: i64) {
801    if fd == 1 {
802        use std::io::Write;
803        let _ = std::io::stdout().flush();
804    } else if fd == 2 {
805        use std::io::Write;
806        let _ = std::io::stderr().flush();
807    }
808}