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: &HashMap<String, Value>) -> Result<(i32, HashMap<String, Value>), 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        let mut final_vars = HashMap::new();
185        for i in 0..len {
186            let slot = &slots_slice[i];
187            let value = match slot.tag {
188                Tag::Int => Value::Int(slot.data as i64),
189                Tag::Float => Value::Float(f64::from_bits(slot.data)),
190                Tag::Ptr => Value::Ptr(slot.data as *mut u8),
191                Tag::Null => Value::Null,
192            };
193            final_vars.insert(vars_vec[i].0.clone(), value);
194        }
195
196        Ok((exit_code, final_vars))
197    }
198}
199
200#[unsafe(no_mangle)]
201pub extern "C" fn lasm_write(fd: i64, ptr: i64, len: i64) -> i64 {
202    if fd == 1 {
203        unsafe {
204            let slice = std::slice::from_raw_parts(ptr as *const u8, len as usize);
205            if let Ok(s) = std::str::from_utf8(slice) {
206                print!("{}", s);
207                use std::io::Write;
208                let _ = std::io::stdout().flush();
209                return len;
210            }
211        }
212    }
213    #[cfg(unix)]
214    unsafe {
215        return libc::write(fd as i32, ptr as *const libc::c_void, len as usize) as i64;
216    }
217    #[cfg(windows)]
218    unsafe {
219        return libc::write(fd as i32, ptr as *const libc::c_void, len as u32) as i64;
220    }
221    #[cfg(not(any(unix, windows)))]
222    {
223        -1
224    }
225}
226
227#[unsafe(no_mangle)]
228pub extern "C" fn lasm_read(fd: i64, ptr: i64, len: i64) -> i64 {
229    #[cfg(unix)]
230    unsafe {
231        return libc::read(fd as i32, ptr as *mut libc::c_void, len as usize) as i64;
232    }
233    #[cfg(windows)]
234    unsafe {
235        return libc::read(fd as i32, ptr as *mut libc::c_void, len as u32) as i64;
236    }
237    #[cfg(not(any(unix, windows)))]
238    {
239        -1
240    }
241}
242
243#[unsafe(no_mangle)]
244pub extern "C" fn write_float(value: f64) -> i64 {
245    print!("{}", value);
246    0
247}
248
249#[unsafe(no_mangle)]
250pub extern "C" fn lasm_malloc(size: i64) -> i64 {
251    use libc::malloc;
252    if size <= 0 {
253        return 0;
254    }
255    unsafe {
256        malloc(size as usize) as i64
257    }
258}
259
260#[unsafe(no_mangle)]
261pub extern "C" fn lasm_free(ptr: i64) {
262    use libc::free;
263    if ptr == 0 {
264        return;
265    }
266    unsafe {
267        free(ptr as *mut libc::c_void);
268    }
269}
270
271#[unsafe(no_mangle)]
272pub extern "C" fn lasm_sleep(milliseconds: i64) {
273    use std::thread;
274    use std::time::Duration;
275    thread::sleep(Duration::from_millis(milliseconds as u64));
276}
277
278#[unsafe(no_mangle)]
279pub extern "C" fn lasm_time() -> i64 {
280    use std::time::{SystemTime, UNIX_EPOCH};
281    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
282    (now.as_secs() as i64 * 1_000_000_000) + now.subsec_nanos() as i64
283}
284
285#[unsafe(no_mangle)]
286pub extern "C" fn lasm_rand(seed: i64) -> i64 {
287    // Simple LCG: seed = (seed * 1103515245 + 12345) % (1 << 31)
288    let mut s = seed as u32;
289    s = ((s as u64 * 1103515245u64 + 12345) % (1u64 << 31)) as u32;
290    let value = s as f64 / (1u32 << 31) as f64;
291    value.to_bits() as i64
292}
293
294#[unsafe(no_mangle)]
295pub extern "C" fn lasm_atoi(ptr: i64) -> i64 {
296    use std::ffi::CStr;
297    if ptr == 0 {
298        return 0;
299    }
300    unsafe {
301        let c_str = CStr::from_ptr(ptr as *const i8);
302        if let Ok(s) = c_str.to_str() {
303            s.parse().unwrap_or(0)
304        } else {
305            0
306        }
307    }
308}
309
310#[unsafe(no_mangle)]
311pub extern "C" fn lasm_system(cmd_ptr: i64) -> i64 {
312    use std::ffi::CStr;
313    use std::process::Command;
314    
315    #[cfg(unix)]
316    unsafe {
317        let cmd_c = CStr::from_ptr(cmd_ptr as *const i8);
318        if let Ok(cmd_s) = cmd_c.to_str() {
319            match Command::new("sh").arg("-c").arg(cmd_s).status() {
320                Ok(status) => status.code().unwrap_or(-1) as i64,
321                Err(_) => -1,
322            }
323        } else {
324            -1
325        }
326    }
327    #[cfg(windows)]
328    unsafe {
329        let cmd_c = CStr::from_ptr(cmd_ptr as *const i8);
330        if let Ok(cmd_s) = cmd_c.to_str() {
331            match Command::new("cmd").arg("/C").arg(cmd_s).status() {
332                Ok(status) => status.code().unwrap_or(-1) as i64,
333                Err(_) => -1,
334            }
335        } else {
336            -1
337        }
338    }
339}
340
341#[unsafe(no_mangle)]
342pub extern "C" fn lasm_ends_with(str_ptr: i64, suffix_ptr: i64) -> i64 {
343    use std::ffi::CStr;
344    unsafe {
345        let str_c = CStr::from_ptr(str_ptr as *const i8);
346        let suffix_c = CStr::from_ptr(suffix_ptr as *const i8);
347        
348        if let (Ok(str_s), Ok(suffix_s)) = (str_c.to_str(), suffix_c.to_str()) {
349            if str_s.ends_with(suffix_s) { 1 } else { 0 }
350        } else {
351            0
352        }
353    }
354}
355
356#[unsafe(no_mangle)]
357pub extern "C" fn lasm_trim(str_ptr: i64, out_ptr: i64) -> i64 {
358    use std::ffi::CStr;
359    unsafe {
360        let str_c = CStr::from_ptr(str_ptr as *const i8);
361        if let Ok(s) = str_c.to_str() {
362            let trimmed = s.trim();
363            let bytes = trimmed.as_bytes();
364            let len = bytes.len().min(255);
365            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
366            *(out_ptr as *mut u8).add(len) = 0;
367            len as i64
368        } else {
369            0
370        }
371    }
372}
373
374#[unsafe(no_mangle)]
375pub extern "C" fn lasm_trim_start(str_ptr: i64, out_ptr: i64) -> i64 {
376    use std::ffi::CStr;
377    unsafe {
378        let str_c = CStr::from_ptr(str_ptr as *const i8);
379        if let Ok(s) = str_c.to_str() {
380            let trimmed = s.trim_start();
381            let bytes = trimmed.as_bytes();
382            let len = bytes.len().min(255);
383            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
384            *(out_ptr as *mut u8).add(len) = 0;
385            len as i64
386        } else {
387            0
388        }
389    }
390}
391
392#[unsafe(no_mangle)]
393pub extern "C" fn lasm_trim_end(str_ptr: i64, out_ptr: i64) -> i64 {
394    use std::ffi::CStr;
395    unsafe {
396        let str_c = CStr::from_ptr(str_ptr as *const i8);
397        if let Ok(s) = str_c.to_str() {
398            let trimmed = s.trim_end();
399            let bytes = trimmed.as_bytes();
400            let len = bytes.len().min(255);
401            std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr as *mut u8, len);
402            *(out_ptr as *mut u8).add(len) = 0;
403            len as i64
404        } else {
405            0
406        }
407    }
408}
409
410#[unsafe(no_mangle)]
411pub extern "C" fn lasm_streq(str1_ptr: i64, str2_ptr: i64) -> i64 {
412    use std::ffi::CStr;
413    unsafe {
414        let str1_c = CStr::from_ptr(str1_ptr as *const i8);
415        let str2_c = CStr::from_ptr(str2_ptr as *const i8);
416        if let (Ok(s1), Ok(s2)) = (str1_c.to_str(), str2_c.to_str()) {
417            if s1 == s2 { 1 } else { 0 }
418        } else {
419            0
420        }
421    }
422}
423
424#[unsafe(no_mangle)]
425pub extern "C" fn lasm_isws(str_ptr: i64) -> i64 {
426    use std::ffi::CStr;
427    unsafe {
428        let str_c = CStr::from_ptr(str_ptr as *const i8);
429        if let Ok(s) = str_c.to_str() {
430            if s.trim().is_empty() { 1 } else { 0 }
431        } else {
432            0
433        }
434    }
435}
436
437#[unsafe(no_mangle)]
438#[allow(unused_variables)]
439pub extern "C" fn lasm_syscall(number: i64, arg0: i64, arg1: i64, arg2: i64, arg3: i64, arg4: i64, arg5: i64) -> i64 {
440    #[cfg(target_os = "macos")]
441    {
442        unsafe { libc::syscall(number as i32, arg0, arg1, arg2, arg3, arg4, arg5) as i64 }
443    }
444
445    #[cfg(target_os = "linux")]
446    {
447        unsafe { libc::syscall(number, arg0, arg1, arg2, arg3, arg4, arg5) }
448    }
449    #[cfg(windows)]
450    {
451        match number {
452            1 => { // write
453                if arg0 == 1 { // stdout
454                    let buf = arg1 as *const u8;
455                    let count = arg2 as usize;
456                    unsafe {
457                        let slice = std::slice::from_raw_parts(buf, count);
458                        if let Ok(s) = std::str::from_utf8(slice) {
459                            print!("{}", s);
460                            std::io::Write::flush(&mut std::io::stdout()).ok();
461                            count as i64
462                        } else {
463                            -1
464                        }
465                    }
466                } else {
467                    -1
468                }
469            }
470            0 => { // read
471                if arg0 == 0 { // stdin
472                    let buf = arg1 as *mut u8;
473                    let count = arg2 as usize;
474                    unsafe {
475                        let mut input = String::new();
476                        if std::io::stdin().read_line(&mut input).is_ok() {
477                            let bytes = input.as_bytes();
478                            let len = bytes.len().min(count);
479                            std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
480                            len as i64
481                        } else {
482                            -1
483                        }
484                    }
485                } else {
486                    -1
487                }
488            }
489            _ => -1
490        }
491    }
492    #[cfg(not(any(unix, windows)))]
493    {
494        -1
495    }
496}
497
498#[unsafe(no_mangle)]
499pub extern "C" fn lasm_print_int(value: i64) {
500    print!("{}", value);
501    use std::io::Write;
502    let _ = std::io::stdout().flush();
503}
504
505#[unsafe(no_mangle)]
506pub extern "C" fn lasm_print_float(bits: i64) {
507    let value = f64::from_bits(bits as u64);
508    print!("{}", value);
509    use std::io::Write;
510    let _ = std::io::stdout().flush();
511}
512
513#[unsafe(no_mangle)]
514pub extern "C" fn lasm_print_str(ptr: i64) {
515    use std::ffi::CStr;
516    if ptr == 0 {
517        return;
518    }
519    unsafe {
520        let c_str = CStr::from_ptr(ptr as *const i8);
521        if let Ok(s) = c_str.to_str() {
522            print!("{}", s);
523        }
524    }
525    use std::io::Write;
526    let _ = std::io::stdout().flush();
527}
528
529#[unsafe(no_mangle)]
530pub extern "C" fn lasm_itoa(value: i64, buf: i64, size: i64) -> i64 {
531    if buf == 0 || size <= 0 {
532        return 0;
533    }
534    let s = format!("{}", value);
535    let bytes = s.as_bytes();
536    let len = bytes.len().min(size as usize - 1);
537    unsafe {
538        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len);
539        *(buf as *mut u8).add(len) = 0;
540    }
541    len as i64
542}
543
544#[unsafe(no_mangle)]
545pub extern "C" fn lasm_fadd(a_bits: i64, b_bits: i64) -> i64 {
546    let a = f64::from_bits(a_bits as u64);
547    let b = f64::from_bits(b_bits as u64);
548    (a + b).to_bits() as i64
549}
550
551#[unsafe(no_mangle)]
552pub extern "C" fn lasm_fsub(a_bits: i64, b_bits: i64) -> i64 {
553    let a = f64::from_bits(a_bits as u64);
554    let b = f64::from_bits(b_bits as u64);
555    (a - b).to_bits() as i64
556}
557
558#[unsafe(no_mangle)]
559pub extern "C" fn lasm_fmul(a_bits: i64, b_bits: i64) -> i64 {
560    let a = f64::from_bits(a_bits as u64);
561    let b = f64::from_bits(b_bits as u64);
562    (a * b).to_bits() as i64
563}
564
565#[unsafe(no_mangle)]
566pub extern "C" fn lasm_fdiv(a_bits: i64, b_bits: i64) -> i64 {
567    let a = f64::from_bits(a_bits as u64);
568    let b = f64::from_bits(b_bits as u64);
569    (a / b).to_bits() as i64
570}
571
572#[unsafe(no_mangle)]
573pub extern "C" fn lasm_itof(value: i64) -> i64 {
574    (value as f64).to_bits() as i64
575}
576
577#[unsafe(no_mangle)]
578pub extern "C" fn lasm_ftoi(bits: i64) -> i64 {
579    let f = f64::from_bits(bits as u64);
580    f as i64
581}
582
583#[unsafe(no_mangle)]
584pub extern "C" fn lasm_fmod(a_bits: i64, b_bits: i64) -> i64 {
585    let a = f64::from_bits(a_bits as u64);
586    let b = f64::from_bits(b_bits as u64);
587    (a % b).to_bits() as i64
588}
589
590#[unsafe(no_mangle)]
591pub extern "C" fn lasm_atof(ptr: i64) -> i64 {
592    use std::ffi::CStr;
593    if ptr == 0 {
594        return 0.0f64.to_bits() as i64;
595    }
596    unsafe {
597        let c_str = CStr::from_ptr(ptr as *const i8);
598        if let Ok(s) = c_str.to_str() {
599            if let Ok(f) = s.parse::<f64>() {
600                f.to_bits() as i64
601            } else {
602                0.0f64.to_bits() as i64
603            }
604        } else {
605            0.0f64.to_bits() as i64
606        }
607    }
608}
609
610#[unsafe(no_mangle)]
611pub extern "C" fn lasm_ftoa(bits: i64, buf: i64, size: i64) -> i64 {
612    if buf == 0 || size <= 0 {
613        return 0;
614    }
615    let f = f64::from_bits(bits as u64);
616    let s = format!("{}", f);
617    let bytes = s.as_bytes();
618    let len = bytes.len().min(size as usize - 1);
619    unsafe {
620        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len);
621        *(buf as *mut u8).add(len) = 0;
622    }
623    len as i64
624}
625
626#[unsafe(no_mangle)]
627pub extern "C" fn lasm_memcpy(dst: i64, src: i64, len: i64) {
628    if dst == 0 || src == 0 || len <= 0 {
629        return;
630    }
631    unsafe {
632        std::ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, len as usize);
633    }
634}
635
636#[unsafe(no_mangle)]
637pub extern "C" fn lasm_memset(dst: i64, val: i64, len: i64) {
638    if dst == 0 || len <= 0 {
639        return;
640    }
641    unsafe {
642        std::ptr::write_bytes(dst as *mut u8, val as u8, len as usize);
643    }
644}
645
646#[unsafe(no_mangle)]
647pub extern "C" fn lasm_fmt_time(total_nanos: i64, fmt_ptr: i64, buffer: i64) -> i64 {
648    use std::ffi::CStr;
649    use std::time::{SystemTime, UNIX_EPOCH};
650
651    if buffer == 0 || fmt_ptr == 0 {
652        return 0;
653    }
654
655    let fmt_str = unsafe {
656        match CStr::from_ptr(fmt_ptr as *const i8).to_str() {
657            Ok(s) => s,
658            Err(_) => "%Y-%m-%d %H:%M:%S",
659        }
660    };
661    
662    let total_seconds = if total_nanos == 0 {
663        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
664        now.as_secs()
665    } else {
666        (total_nanos / 1_000_000_000) as u64
667    };
668    
669    let _nanoseconds = if total_nanos == 0 {
670        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
671        now.subsec_nanos()
672    } else {
673        (total_nanos % 1_000_000_000) as u32
674    };
675    
676    let days_since_epoch = total_seconds / 86400;
677    let mut year = 1970;
678    let mut days = days_since_epoch;
679    
680    loop {
681        let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
682        let days_in_year = if is_leap { 366 } else { 365 };
683        if days >= days_in_year {
684            days -= days_in_year;
685            year += 1;
686        } else {
687            break;
688        }
689    }
690    
691    let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
692    let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
693    let mut month = 1;
694    let mut day = days + 1;
695    
696    for (i, &days_in_month) in month_days.iter().enumerate() {
697        let actual_days = if i == 1 && is_leap { days_in_month + 1 } else { days_in_month };
698        if day > actual_days {
699            day -= actual_days;
700            month += 1;
701        } else {
702            break;
703        }
704    }
705    
706    let seconds_in_day = total_seconds % 86400;
707    let hour = seconds_in_day / 3600;
708    let minute = (seconds_in_day % 3600) / 60;
709    let second = seconds_in_day % 60;
710    
711    let mut result = String::new();
712    let mut chars = fmt_str.chars().peekable();
713    
714    while let Some(ch) = chars.next() {
715        if ch == '%' {
716            if let Some(spec) = chars.next() {
717                match spec {
718                    'Y' => result.push_str(&format!("{:04}", year)),
719                    'm' => result.push_str(&format!("{:02}", month)),
720                    'd' => result.push_str(&format!("{:02}", day)),
721                    'H' => result.push_str(&format!("{:02}", hour)),
722                    'M' => result.push_str(&format!("{:02}", minute)),
723                    'S' => result.push_str(&format!("{:02}", second)),
724                    '%' => result.push('%'),
725                    _ => { result.push('%'); result.push(spec); }
726                }
727            } else {
728                result.push('%');
729            }
730        } else {
731            result.push(ch);
732        }
733    }
734    
735    let bytes = result.as_bytes();
736    let len = bytes.len().min(255);
737    
738    unsafe {
739        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer as *mut u8, len);
740        *(buffer as *mut u8).add(len) = 0;
741    }
742    
743    len as i64
744}
745
746#[unsafe(no_mangle)]
747pub extern "C" fn lasm_starts_with(str_ptr: i64, prefix_ptr: i64) -> i64 {
748    use std::ffi::CStr;
749    if str_ptr == 0 || prefix_ptr == 0 {
750        return 0;
751    }
752    unsafe {
753        let str_cstr = CStr::from_ptr(str_ptr as *const i8);
754        let prefix_cstr = CStr::from_ptr(prefix_ptr as *const i8);
755        if let (Ok(s), Ok(p)) = (str_cstr.to_str(), prefix_cstr.to_str()) {
756            if s.starts_with(p) { 1 } else { 0 }
757        } else {
758            0
759        }
760    }
761}
762
763#[unsafe(no_mangle)]
764pub extern "C" fn lasm_str_len(str_ptr: i64) -> i64 {
765    use std::ffi::CStr;
766    if str_ptr == 0 {
767        return 0;
768    }
769    unsafe {
770        CStr::from_ptr(str_ptr as *const i8).to_bytes().len() as i64
771    }
772}
773
774#[unsafe(no_mangle)]
775pub extern "C" fn lasm_strcmp(str1_ptr: i64, str2_ptr: i64) -> i64 {
776    if str1_ptr == 0 || str2_ptr == 0 {
777        return if str1_ptr == str2_ptr { 0 } else { 1 };
778    }
779    unsafe {
780        let s1 = CStr::from_ptr(str1_ptr as *const i8).to_bytes();
781        let s2 = CStr::from_ptr(str2_ptr as *const i8).to_bytes();
782        s1.cmp(s2) as i64
783    }
784}
785
786#[unsafe(no_mangle)]
787pub extern "C" fn lasm_strcpy(dst: i64, src: i64) -> i64 {
788    if dst == 0 || src == 0 {
789        return 0;
790    }
791    unsafe {
792        let src_str = CStr::from_ptr(src as *const i8).to_bytes();
793        let dst_slice = std::slice::from_raw_parts_mut(dst as *mut u8, src_str.len() + 1);
794        dst_slice[..src_str.len()].copy_from_slice(src_str);
795        dst_slice[src_str.len()] = 0;
796        dst
797    }
798}
799
800#[unsafe(no_mangle)]
801pub extern "C" fn lasm_flush(fd: i64) {
802    if fd == 1 {
803        use std::io::Write;
804        let _ = std::io::stdout().flush();
805    } else if fd == 2 {
806        use std::io::Write;
807        let _ = std::io::stderr().flush();
808    }
809}